From 236f974087acd51dfb66ee11ddfc928e05ab8567 Mon Sep 17 00:00:00 2001 From: Imko Marijnissen Date: Fri, 12 Sep 2025 16:46:47 +0900 Subject: [PATCH 01/17] feat: updating generics to be variable for cumulative --- .../core/src/constraints/cumulative.rs | 99 ++++----- .../time_table/explanations/big_step.rs | 28 ++- .../cumulative/time_table/explanations/mod.rs | 32 ++- .../time_table/explanations/naive.rs | 25 ++- .../time_table/explanations/pointwise.rs | 44 ++-- .../checks.rs | 68 ++++--- .../debug.rs | 28 ++- .../insertion.rs | 20 +- .../removal.rs | 51 +++-- .../synchronisation.rs | 46 +++-- .../time_table_over_interval_incremental.rs | 69 +++++-- .../synchronisation.rs | 64 ++++-- .../time_table_per_point_incremental.rs | 61 ++++-- .../time_table/propagation_handler.rs | 50 +++-- .../time_table/time_table_over_interval.rs | 104 +++++++--- .../time_table/time_table_per_point.rs | 63 ++++-- .../cumulative/time_table/time_table_util.rs | 189 ++++++++++++------ .../structs/mandatory_part_adjustments.rs | 2 +- .../cumulative/utils/structs/parameters.rs | 20 +- .../utils/structs/resource_profile.rs | 25 +-- .../cumulative/utils/structs/task.rs | 54 +++-- .../utils/structs/updatable_structures.rs | 66 +++--- .../utils/structs/updated_task_info.rs | 4 +- .../src/propagators/cumulative/utils/util.rs | 36 +++- 24 files changed, 820 insertions(+), 428 deletions(-) diff --git a/pumpkin-crates/core/src/constraints/cumulative.rs b/pumpkin-crates/core/src/constraints/cumulative.rs index 981ba0a91..46986d62e 100644 --- a/pumpkin-crates/core/src/constraints/cumulative.rs +++ b/pumpkin-crates/core/src/constraints/cumulative.rs @@ -123,26 +123,18 @@ use crate::Solver; /// cumulative constraint’, in Principles and Practice of Constraint Programming: 21st /// International Conference, CP 2015, Cork, Ireland, August 31--September 4, 2015, Proceedings /// 21, 2015, pp. 149–157. -pub fn cumulative( - start_times: StartTimes, - durations: Durations, - resource_requirements: ResourceRequirements, - resource_capacity: i32, +pub fn cumulative< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, +>( + tasks: impl IntoIterator>, + resource_capacity: CVar, constraint_tag: ConstraintTag, -) -> impl Constraint -where - StartTimes: IntoIterator, - StartTimes::Item: IntegerVariable + Debug + 'static, - StartTimes::IntoIter: ExactSizeIterator, - Durations: IntoIterator, - Durations::IntoIter: ExactSizeIterator, - ResourceRequirements: IntoIterator, - ResourceRequirements::IntoIter: ExactSizeIterator, -{ +) -> impl Constraint { cumulative_with_options( - start_times, - durations, - resource_requirements, + tasks, resource_capacity, CumulativeOptions::default(), constraint_tag, @@ -153,59 +145,42 @@ where /// with the provided [`CumulativeOptions`]. /// /// See the documentation of [`cumulative`] for more information about the constraint. -pub fn cumulative_with_options( - start_times: StartTimes, - durations: Durations, - resource_requirements: ResourceRequirements, - resource_capacity: i32, +pub fn cumulative_with_options< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, +>( + tasks: impl IntoIterator>, + resource_capacity: CVar, options: CumulativeOptions, constraint_tag: ConstraintTag, -) -> impl Constraint -where - StartTimes: IntoIterator, - StartTimes::Item: IntegerVariable + Debug + 'static, - StartTimes::IntoIter: ExactSizeIterator, - Durations: IntoIterator, - Durations::IntoIter: ExactSizeIterator, - ResourceRequirements: IntoIterator, - ResourceRequirements::IntoIter: ExactSizeIterator, -{ - let start_times = start_times.into_iter(); - let durations = durations.into_iter(); - let resource_requirements = resource_requirements.into_iter(); - - pumpkin_assert_simple!( - start_times.len() == durations.len() && durations.len() == resource_requirements.len(), - "The number of start variables, durations and resource requirements should be the same!" - ); - - CumulativeConstraint::new( - &start_times - .zip(durations) - .zip(resource_requirements) - .map(|((start_time, duration), resource_requirement)| ArgTask { - start_time, - processing_time: duration, - resource_usage: resource_requirement, - }) - .collect::>(), +) -> impl Constraint { + CumulativeConstraint::::new( + &tasks.into_iter().collect::>(), resource_capacity, options, constraint_tag, ) } -struct CumulativeConstraint { - tasks: Vec>, - resource_capacity: i32, +struct CumulativeConstraint { + tasks: Vec>, + resource_capacity: CVar, options: CumulativeOptions, constraint_tag: ConstraintTag, } -impl CumulativeConstraint { +impl< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, + > CumulativeConstraint +{ fn new( - tasks: &[ArgTask], - resource_capacity: i32, + tasks: &[ArgTask], + resource_capacity: CVar, options: CumulativeOptions, constraint_tag: ConstraintTag, ) -> Self { @@ -219,7 +194,13 @@ impl CumulativeConstraint { } } -impl Constraint for CumulativeConstraint { +impl< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, + > Constraint for CumulativeConstraint +{ fn post(self, solver: &mut Solver) -> Result<(), ConstraintOperationError> { match self.options.propagation_method { CumulativePropagationMethod::TimeTablePerPoint => TimeTablePerPointPropagator::new( diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/big_step.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/big_step.rs index 902e8a6cc..893a2c38b 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/big_step.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/big_step.rs @@ -12,8 +12,12 @@ use crate::variables::IntegerVariable; /// Creates the propagation explanation using the big-step approach (see /// [`CumulativeExplanationType::BigStep`]) -pub(crate) fn create_big_step_propagation_explanation( - profile: &ResourceProfile, +pub(crate) fn create_big_step_propagation_explanation< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( + profile: &ResourceProfile, ) -> PropositionalConjunction { profile .profile_tasks @@ -31,8 +35,12 @@ pub(crate) fn create_big_step_propagation_explanation( - conflict_profile: &ResourceProfile, +pub(crate) fn create_big_step_conflict_explanation< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( + conflict_profile: &ResourceProfile, ) -> PropositionalConjunction { conflict_profile .profile_tasks @@ -49,9 +57,9 @@ pub(crate) fn create_big_step_conflict_explanation( - task: &Rc>, - profile: &ResourceProfile, +pub(crate) fn create_big_step_predicate_propagating_task_lower_bound_propagation( + task: &Rc>, + profile: &ResourceProfile, ) -> Predicate where Var: IntegerVariable + 'static, @@ -59,9 +67,9 @@ where predicate!(task.start_variable >= profile.start + 1 - task.processing_time) } -pub(crate) fn create_big_step_predicate_propagating_task_upper_bound_propagation( - task: &Rc>, - profile: &ResourceProfile, +pub(crate) fn create_big_step_predicate_propagating_task_upper_bound_propagation( + task: &Rc>, + profile: &ResourceProfile, context: PropagationContext, ) -> Predicate where diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/mod.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/mod.rs index 08c315662..815032e59 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/mod.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/mod.rs @@ -71,11 +71,13 @@ impl Display for CumulativeExplanationType { /// Creates the lower-bound [`Predicate`] of the `propagating_task` based on the `explanation_type`. pub(crate) fn create_predicate_propagating_task_lower_bound_propagation< Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, >( explanation_type: CumulativeExplanationType, context: PropagationContext, - task: &Rc>, - profile: &ResourceProfile, + task: &Rc>, + profile: &ResourceProfile, time_point: Option, ) -> Predicate { match explanation_type { @@ -92,12 +94,16 @@ pub(crate) fn create_predicate_propagating_task_lower_bound_propagation< } /// Adds the lower-bound predicate of the propagating task to the provided `explanation`. -pub(crate) fn add_propagating_task_predicate_lower_bound( +pub(crate) fn add_propagating_task_predicate_lower_bound< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( mut explanation: PropositionalConjunction, explanation_type: CumulativeExplanationType, context: PropagationContext, - task: &Rc>, - profile: &ResourceProfile, + task: &Rc>, + profile: &ResourceProfile, time_point: Option, ) -> PropositionalConjunction { explanation.add(create_predicate_propagating_task_lower_bound_propagation( @@ -113,11 +119,13 @@ pub(crate) fn add_propagating_task_predicate_lower_bound( explanation_type: CumulativeExplanationType, context: PropagationContext, - task: &Rc>, - profile: &ResourceProfile, + task: &Rc>, + profile: &ResourceProfile, time_point: Option, ) -> Predicate { match explanation_type { @@ -136,12 +144,16 @@ pub(crate) fn create_predicate_propagating_task_upper_bound_propagation< } /// Adds the upper-bound predicate of the propagating task to the provided `explanation`. -pub(crate) fn add_propagating_task_predicate_upper_bound( +pub(crate) fn add_propagating_task_predicate_upper_bound< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( mut explanation: PropositionalConjunction, explanation_type: CumulativeExplanationType, context: PropagationContext, - task: &Rc>, - profile: &ResourceProfile, + task: &Rc>, + profile: &ResourceProfile, time_point: Option, ) -> PropositionalConjunction { explanation.add(create_predicate_propagating_task_upper_bound_propagation( diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/naive.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/naive.rs index c9761ce5c..2e1679520 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/naive.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/naive.rs @@ -11,8 +11,12 @@ use crate::variables::IntegerVariable; /// Creates the propagation explanation using the naive approach (see /// [`CumulativeExplanationType::Naive`]) -pub(crate) fn create_naive_propagation_explanation( - profile: &ResourceProfile, +pub(crate) fn create_naive_propagation_explanation< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( + profile: &ResourceProfile, context: PropagationContext, ) -> PropositionalConjunction { profile @@ -35,8 +39,13 @@ pub(crate) fn create_naive_propagation_explanation( - conflict_profile: &ResourceProfile, +pub(crate) fn create_naive_conflict_explanation< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + Context: ReadDomains + Copy, +>( + conflict_profile: &ResourceProfile, context: Context, ) -> PropositionalConjunction where @@ -60,9 +69,9 @@ where .collect() } -pub(crate) fn create_naive_predicate_propagating_task_lower_bound_propagation( +pub(crate) fn create_naive_predicate_propagating_task_lower_bound_propagation( context: PropagationContext, - task: &Rc>, + task: &Rc>, ) -> Predicate where Var: IntegerVariable + 'static, @@ -70,9 +79,9 @@ where predicate!(task.start_variable >= context.lower_bound(&task.start_variable)) } -pub(crate) fn create_naive_predicate_propagating_task_upper_bound_propagation( +pub(crate) fn create_naive_predicate_propagating_task_upper_bound_propagation( context: PropagationContext, - task: &Rc>, + task: &Rc>, ) -> Predicate where Var: IntegerVariable + 'static, diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/pointwise.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/pointwise.rs index 8591b0ac0..460ab316c 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/pointwise.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/pointwise.rs @@ -17,10 +17,14 @@ use crate::pumpkin_assert_extreme; use crate::pumpkin_assert_simple; use crate::variables::IntegerVariable; -pub(crate) fn propagate_lower_bounds_with_pointwise_explanations( +pub(crate) fn propagate_lower_bounds_with_pointwise_explanations< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( context: &mut PropagationContextMut, - profiles: &[&ResourceProfile], - propagating_task: &Rc>, + profiles: &[&ResourceProfile], + propagating_task: &Rc>, inference_code: InferenceCode, ) -> Result<(), EmptyDomain> { // The time points should follow the following properties (based on `Improving @@ -126,10 +130,14 @@ pub(crate) fn propagate_lower_bounds_with_pointwise_explanations( +pub(crate) fn propagate_upper_bounds_with_pointwise_explanations< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( context: &mut PropagationContextMut, - profiles: &[&ResourceProfile], - propagating_task: &Rc>, + profiles: &[&ResourceProfile], + propagating_task: &Rc>, inference_code: InferenceCode, ) -> Result<(), EmptyDomain> { // The time points should follow the following properties (based on `Improving @@ -235,9 +243,13 @@ pub(crate) fn propagate_upper_bounds_with_pointwise_explanations( +pub(crate) fn create_pointwise_propagation_explanation< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( time_point: i32, - profile: &ResourceProfile, + profile: &ResourceProfile, ) -> PropositionalConjunction { profile .profile_tasks @@ -255,8 +267,12 @@ pub(crate) fn create_pointwise_propagation_explanation( - conflict_profile: &ResourceProfile, +pub(crate) fn create_pointwise_conflict_explanation< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( + conflict_profile: &ResourceProfile, ) -> PropositionalConjunction { // As stated in improving scheduling by learning, we choose the middle point; this // could potentially be improved @@ -279,8 +295,8 @@ pub(crate) fn create_pointwise_conflict_explanation( - task: &Rc>, +pub(crate) fn create_pointwise_predicate_propagating_task_lower_bound_propagation( + task: &Rc>, time_point: Option, ) -> Predicate where @@ -295,8 +311,8 @@ where ) } -pub(crate) fn create_pointwise_predicate_propagating_task_upper_bound_propagation( - task: &Rc>, +pub(crate) fn create_pointwise_predicate_propagating_task_upper_bound_propagation( + task: &Rc>, time_point: Option, ) -> Predicate where diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/checks.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/checks.rs index 59ff17cc0..74e6aac03 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/checks.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/checks.rs @@ -12,13 +12,17 @@ use crate::variables::IntegerVariable; /// Determines whether the added mandatory part causes a new profile before the first overapping /// profile. -pub(crate) fn new_profile_before_first_profile( +pub(crate) fn new_profile_before_first_profile< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( current_index: usize, start_index: usize, update_range: &Range, - profile: &ResourceProfile, - to_add: &mut Vec>, - task: &Rc>, + profile: &ResourceProfile, + to_add: &mut Vec>, + task: &Rc>, ) { if current_index == start_index && update_range.start < profile.start { // We are considering the first overlapping profile and there is @@ -37,14 +41,18 @@ pub(crate) fn new_profile_before_first_profile( /// Determines whether a new profile should be inserted between the current profile (pointed to /// by `current_index`) and the previous profile. -pub(crate) fn new_profile_between_profiles( - time_table: &OverIntervalTimeTableType, +pub(crate) fn new_profile_between_profiles< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( + time_table: &OverIntervalTimeTableType, current_index: usize, start_index: usize, update_range: &Range, - profile: &ResourceProfile, - to_add: &mut Vec>, - task: &Rc>, + profile: &ResourceProfile, + to_add: &mut Vec>, + task: &Rc>, ) { if current_index != start_index && current_index != 0 { // We are not considering the first profile and there could be a @@ -80,10 +88,12 @@ pub(crate) fn new_profile_between_profiles( /// is added in [`overlap_updated_profile`]. pub(crate) fn split_profile_added_part_starts_after_profile_start< Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, >( update_range: &Range, - profile: &ResourceProfile, - to_add: &mut Vec>, + profile: &ResourceProfile, + to_add: &mut Vec>, ) { if update_range.start > profile.start { // We are splitting the current profile into one or more parts @@ -106,13 +116,17 @@ pub(crate) fn split_profile_added_part_starts_after_profile_start< /// Determines whether a new profile which contains the overlap between `profile` and the added /// mandatory part should be added. -pub(crate) fn overlap_updated_profile( +pub(crate) fn overlap_updated_profile< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( update_range: &Range, - profile: &ResourceProfile, - to_add: &mut Vec>, - task: &Rc>, + profile: &ResourceProfile, + to_add: &mut Vec>, + task: &Rc>, capacity: i32, -) -> Result<(), ResourceProfile> { +) -> Result<(), ResourceProfile> { // Now we create a new profile which consists of the part of the // profile covered by the update range // This means that we are adding the contribution of the updated @@ -168,10 +182,14 @@ pub(crate) fn overlap_updated_profile( /// Note that this function adds the unchanged part only (i.e. the part of the profile with /// which the added mandatory part does **not** overlap), the updated part of this profile /// is added in [`overlap_updated_profile`]. -pub(crate) fn split_profile_added_part_ends_before_profile_end( +pub(crate) fn split_profile_added_part_ends_before_profile_end< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( update_range: &Range, - profile: &ResourceProfile, - to_add: &mut Vec>, + profile: &ResourceProfile, + to_add: &mut Vec>, ) { if profile.end >= update_range.end { // We are splitting the current profile into one or more parts @@ -190,13 +208,17 @@ pub(crate) fn split_profile_added_part_ends_before_profile_end( +pub(crate) fn new_part_after_last_profile< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( current_index: usize, end_index: usize, update_range: &Range, - profile: &ResourceProfile, - to_add: &mut Vec>, - task: &Rc>, + profile: &ResourceProfile, + to_add: &mut Vec>, + task: &Rc>, ) { if current_index == end_index && update_range.end > profile.end + 1 { // We are considering the last overlapping profile and there is diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/debug.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/debug.rs index 613be837e..89fea8dd4 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/debug.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/debug.rs @@ -23,12 +23,15 @@ use crate::variables::IntegerVariable; /// same! pub(crate) fn time_tables_are_the_same_interval< Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, const SYNCHRONISE: bool, >( context: PropagationContext, inference_code: InferenceCode, - time_table: &OverIntervalTimeTableType, - parameters: &CumulativeParameters, + time_table: &OverIntervalTimeTableType, + parameters: &CumulativeParameters, ) -> bool { let time_table_scratch = create_time_table_over_interval_from_scratch(context, parameters, inference_code) @@ -80,8 +83,13 @@ pub(crate) fn time_tables_are_the_same_interval< /// Merge all mergeable profiles (see [`are_mergeable`]) going from `[start_index, end_index]` /// in the provided `time_table`. -pub(crate) fn merge_profiles( - time_table: &mut OverIntervalTimeTableType, +pub(crate) fn merge_profiles< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, +>( + time_table: &mut OverIntervalTimeTableType, start_index: usize, end_index: usize, ) { @@ -97,7 +105,7 @@ pub(crate) fn merge_profiles( // To avoid needless splicing, we keep track of the range at which insertions will take place let mut insertion_range: Option> = None; // And the profiles which need to be added - let mut to_add: Option>> = None; + let mut to_add: Option>> = None; // We go over all pairs of profiles, starting from start index until end index while current_index < end { @@ -179,9 +187,13 @@ pub(crate) fn merge_profiles( /// time-table created from scratch. /// /// It is assumed that the profile tasks of both profiles do not contain duplicates -pub(crate) fn are_mergeable( - first_profile: &ResourceProfile, - second_profile: &ResourceProfile, +pub(crate) fn are_mergeable< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( + first_profile: &ResourceProfile, + second_profile: &ResourceProfile, ) -> bool { pumpkin_assert_extreme!( first_profile diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/insertion.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/insertion.rs index 3fbda9ee4..a61c1de66 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/insertion.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/insertion.rs @@ -16,14 +16,17 @@ use crate::variables::IntegerVariable; /// profiles and adds them to the `time-table` at the correct position. pub(crate) fn insert_profiles_overlapping_with_added_mandatory_part< Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, >( - time_table: &mut OverIntervalTimeTableType, + time_table: &mut OverIntervalTimeTableType, start_index: usize, end_index: usize, update_range: &Range, - updated_task: &Rc>, + updated_task: &Rc>, capacity: i32, -) -> Result<(), ResourceProfile> { +) -> Result<(), ResourceProfile> { let mut to_add = Vec::new(); // We keep track of whether a conflict has been found @@ -115,11 +118,16 @@ pub(crate) fn insert_profiles_overlapping_with_added_mandatory_part< /// The new mandatory part added by `updated_task` (spanning `update_range`) does not overlap /// with any existing profile. This method inserts it at the position of `index_to_insert` /// in the `time-table`. -pub(crate) fn insert_profile_new_mandatory_part( - time_table: &mut OverIntervalTimeTableType, +pub(crate) fn insert_profile_new_mandatory_part< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, +>( + time_table: &mut OverIntervalTimeTableType, index_to_insert: usize, update_range: &Range, - updated_task: &Rc>, + updated_task: &Rc>, ) { pumpkin_assert_moderate!( index_to_insert <= time_table.len() diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/removal.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/removal.rs index dc9dfa4af..221074fbd 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/removal.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/removal.rs @@ -15,12 +15,15 @@ use crate::variables::IntegerVariable; /// profiles and adds them to the `time-table` at the correct position. pub(crate) fn reduce_profiles_overlapping_with_added_mandatory_part< Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, >( - time_table: &mut OverIntervalTimeTableType, + time_table: &mut OverIntervalTimeTableType, start_index: usize, end_index: usize, update_range: &Range, - updated_task: &Rc>, + updated_task: &Rc>, ) { let mut to_add = vec![]; @@ -51,12 +54,16 @@ pub(crate) fn reduce_profiles_overlapping_with_added_mandatory_part< } /// Returns the provided `profile` with the provided `updated_task` removed. -fn remove_task_from_profile( - updated_task: &Rc>, +fn remove_task_from_profile< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( + updated_task: &Rc>, start: i32, end: i32, - profile: &ResourceProfile, -) -> ResourceProfile { + profile: &ResourceProfile, +) -> ResourceProfile { let mut updated_profile_tasks = profile.profile_tasks.clone(); let _ = updated_profile_tasks.swap_remove( updated_profile_tasks @@ -75,10 +82,14 @@ fn remove_task_from_profile( /// If there is a partial overlap, this method creates a profile consisting of the original /// profile before the overlap. -pub(crate) fn split_first_profile( - to_add: &mut Vec>, +pub(crate) fn split_first_profile< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( + to_add: &mut Vec>, update_range: &Range, - first_profile: &ResourceProfile, + first_profile: &ResourceProfile, ) { if update_range.start > first_profile.start { to_add.push(ResourceProfile { @@ -90,10 +101,14 @@ pub(crate) fn split_first_profile( } } -pub(crate) fn split_last_profile( - to_add: &mut Vec>, +pub(crate) fn split_last_profile< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( + to_add: &mut Vec>, update_range: &Range, - last_profile: &ResourceProfile, + last_profile: &ResourceProfile, ) { if last_profile.end >= update_range.end { // We are splitting the current profile into one or more parts @@ -111,11 +126,15 @@ pub(crate) fn split_last_profile( } /// This method creates a new profile based on the overlap with the provided `profile`. -pub(crate) fn overlap_updated_profile( +pub(crate) fn overlap_updated_profile< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( update_range: &Range, - profile: &ResourceProfile, - to_add: &mut Vec>, - updated_task: &Rc>, + profile: &ResourceProfile, + to_add: &mut Vec>, + updated_task: &Rc>, ) { if profile.height - updated_task.resource_usage == 0 { // If the removal of this task results in an empty profile then we simply do not add it diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/synchronisation.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/synchronisation.rs index 74cb3d619..82ba0ffb4 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/synchronisation.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/synchronisation.rs @@ -20,10 +20,15 @@ use crate::variables::IntegerVariable; /// [`TimeTableOverIntervalPropagator`]; this is the first conflicting profile in terms of start /// time, however, the returned profile should be merged with adjacent profiles to create the /// returned conflict profile. -pub(crate) fn find_synchronised_conflict( - time_table: &mut OverIntervalTimeTableType, - parameters: &CumulativeParameters, -) -> Option> { +pub(crate) fn find_synchronised_conflict< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, +>( + time_table: &mut OverIntervalTimeTableType, + parameters: &CumulativeParameters, +) -> Option> { if time_table.is_empty() { return None; } @@ -55,10 +60,13 @@ pub(crate) fn find_synchronised_conflict( /// [`TimeTableOverIntervalPropagator`]. pub(crate) fn check_synchronisation_conflict_explanation_over_interval< Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, >( synchronised_conflict_explanation: &PropagationStatusCP, context: PropagationContext, - parameters: &CumulativeParameters, + parameters: &CumulativeParameters, inference_code: InferenceCode, ) -> bool { let error_from_scratch = @@ -80,11 +88,16 @@ pub(crate) fn check_synchronisation_conflict_explanation_over_interval< /// by [`TimeTableOverIntervalPropagator`]), this function calculates the error which would have /// been reported by [`TimeTableOverIntervalPropagator`] by finding the tasks which should be /// included in the profile and sorting them in the same order. -pub(crate) fn create_synchronised_conflict_explanation( +pub(crate) fn create_synchronised_conflict_explanation< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, +>( context: PropagationContext, inference_code: InferenceCode, - conflicting_profile: &mut ResourceProfile, - parameters: &CumulativeParameters, + conflicting_profile: &mut ResourceProfile, + parameters: &CumulativeParameters, ) -> PropagationStatusCP { // If we need to synchronise then we need to find the conflict profile which // would have been found by the non-incremental propagator; we thus first sort based on @@ -125,8 +138,13 @@ pub(crate) fn create_synchronised_conflict_explanation( - time_table: &mut OverIntervalTimeTableType, +pub(crate) fn synchronise_time_table< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, +>( + time_table: &mut OverIntervalTimeTableType, context: PropagationContext, ) { if !time_table.is_empty() { @@ -143,8 +161,12 @@ pub(crate) fn synchronise_time_table( /// Sorts the provided `profile` on non-decreasing order of upper-bound while tie-breaking in /// non-decreasing order of ID -fn sort_profile_based_on_upper_bound_and_id( - profile: &mut ResourceProfile, +fn sort_profile_based_on_upper_bound_and_id< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( + profile: &mut ResourceProfile, context: PropagationContext, ) { profile.profile_tasks.sort_by(|a, b| { diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/time_table_over_interval_incremental.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/time_table_over_interval_incremental.rs index 01e36279c..c6fd88d63 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/time_table_over_interval_incremental.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/time_table_over_interval_incremental.rs @@ -74,16 +74,22 @@ use crate::pumpkin_assert_simple; /// \[1\] A. Schutt, Improving scheduling by learning. University of Melbourne, Department of /// Computer Science and Software Engineering, 2011. #[derive(Clone, Debug)] -pub(crate) struct TimeTableOverIntervalIncrementalPropagator { +pub(crate) struct TimeTableOverIntervalIncrementalPropagator< + Var, + PVar, + RVar, + CVar, + const SYNCHRONISE: bool, +> { /// The key `t` (representing a time-point) holds the mandatory resource consumption of /// [`Task`]s at that time (stored in a [`ResourceProfile`]); the [`ResourceProfile`]s are /// sorted based on start time and they are assumed to be non-overlapping - time_table: OverIntervalTimeTableType, + time_table: OverIntervalTimeTableType, /// Stores the input parameters to the cumulative constraint - parameters: CumulativeParameters, + parameters: CumulativeParameters, /// Stores structures which change during the search; either to store bounds or when applying /// incrementality - updatable_structures: UpdatableStructures, + updatable_structures: UpdatableStructures, /// Stores whether the propagator found a conflict in the previous call /// /// This is stored to deal with the case where the same conflict can be created via two @@ -101,8 +107,14 @@ pub(crate) struct TimeTableOverIntervalIncrementalPropagator, } -impl PropagatorConstructor - for TimeTableOverIntervalIncrementalPropagator +impl< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, + const SYNCHRONISE: bool, + > PropagatorConstructor + for TimeTableOverIntervalIncrementalPropagator { type PropagatorImpl = Self; @@ -127,15 +139,20 @@ impl PropagatorConstruc } } -impl - TimeTableOverIntervalIncrementalPropagator +impl< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, + const SYNCHRONISE: bool, + > TimeTableOverIntervalIncrementalPropagator { pub(crate) fn new( - arg_tasks: &[ArgTask], + arg_tasks: &[ArgTask], capacity: i32, cumulative_options: CumulativePropagatorOptions, constraint_tag: ConstraintTag, - ) -> TimeTableOverIntervalIncrementalPropagator { + ) -> TimeTableOverIntervalIncrementalPropagator { let tasks = create_tasks(arg_tasks); let parameters = CumulativeParameters::new(tasks, capacity, cumulative_options); let updatable_structures = UpdatableStructures::new(¶meters); @@ -157,7 +174,7 @@ impl &mut self, context: PropagationContext, mandatory_part_adjustments: &MandatoryPartAdjustments, - task: &Rc>, + task: &Rc>, ) -> PropagationStatusCP { let mut conflict = None; // We consider both of the possible update ranges @@ -206,7 +223,7 @@ impl fn remove_from_time_table( &mut self, mandatory_part_adjustments: &MandatoryPartAdjustments, - task: &Rc>, + task: &Rc>, ) { // We consider both of the possible update ranges // Note that the upper update range is first considered to avoid any issues with the @@ -371,8 +388,14 @@ impl } } -impl Propagator - for TimeTableOverIntervalIncrementalPropagator +impl< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, + const SYNCHRONISE: bool, + > Propagator + for TimeTableOverIntervalIncrementalPropagator { fn propagate(&mut self, mut context: PropagationContextMut) -> PropagationStatusCP { pumpkin_assert_advanced!( @@ -533,8 +556,13 @@ impl Propagator /// if there are no overlapping profiles /// /// Note that the lower-bound of the range is inclusive and the upper-bound is exclusive -fn determine_profiles_to_update( - time_table: &OverIntervalTimeTableType, +fn determine_profiles_to_update< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, +>( + time_table: &OverIntervalTimeTableType, update_range: &Range, ) -> Result<(usize, usize), usize> { let overlapping_profile = find_overlapping_profile(time_table, update_range); @@ -602,8 +630,13 @@ fn determine_profiles_to_update( /// [Ok] containing the index of the overlapping profile. If no such element could be found, /// it returns [Err] containing the index at which the element should be inserted to /// preserve the ordering -fn find_overlapping_profile( - time_table: &OverIntervalTimeTableType, +fn find_overlapping_profile< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, +>( + time_table: &OverIntervalTimeTableType, update_range: &Range, ) -> Result { time_table.binary_search_by(|profile| { diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/synchronisation.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/synchronisation.rs index 683b214ce..88732ccc2 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/synchronisation.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/synchronisation.rs @@ -2,6 +2,7 @@ use std::rc::Rc; use crate::basic_types::Inconsistency; use crate::basic_types::PropagationStatusCP; +use crate::engine::cp::propagation::contexts::propagation_context::ReadDomains; use crate::engine::propagation::PropagationContext; use crate::proof::InferenceCode; use crate::propagators::create_time_table_per_point_from_scratch; @@ -18,11 +19,14 @@ use crate::variables::IntegerVariable; /// [`TimeTablePerPointPropagator`]. pub(crate) fn check_synchronisation_conflict_explanation_per_point< Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, >( synchronised_conflict_explanation: &PropagationStatusCP, context: PropagationContext, inference_code: InferenceCode, - parameters: &CumulativeParameters, + parameters: &CumulativeParameters, ) -> bool { let error_from_scratch = create_time_table_per_point_from_scratch(context, inference_code, parameters); @@ -42,9 +46,14 @@ pub(crate) fn check_synchronisation_conflict_explanation_per_point< /// Finds the conflicting profile which would have been found by the /// [`TimeTablePerPointPropagator`]; this is the conflicting profile which has the minimum maximum /// ID in set of the first `n` profile tasks (when sorted on ID) which overflow the capacity -pub(crate) fn find_synchronised_conflict( - time_table: &mut PerPointTimeTableType, - parameters: &CumulativeParameters, +pub(crate) fn find_synchronised_conflict< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, +>( + time_table: &mut PerPointTimeTableType, + parameters: &CumulativeParameters, ) -> Option { let mut profile_time_point = None; let mut minimum_maximum_id = u32::MAX; @@ -79,11 +88,18 @@ pub(crate) fn find_synchronised_conflict( /// /// The sum of the heights of the tasks is stored in the provided `output_height`; note that this /// means that the iterator should be consumed before reading the `output_height` -fn get_minimum_set_of_tasks_which_overflow_capacity<'a, Var: IntegerVariable + 'static>( - profile: &'a mut ResourceProfile, - parameters: &'a CumulativeParameters, +fn get_minimum_set_of_tasks_which_overflow_capacity< + 'a, + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, +>( + context: PropagationContext<'a>, + profile: &'a mut ResourceProfile, + parameters: &'a CumulativeParameters, output_height: &'a mut i32, -) -> impl Iterator>> + 'a { +) -> impl Iterator>> + 'a { // First we sort the profile based on the ID sort_profile_based_on_id(profile); @@ -94,10 +110,10 @@ fn get_minimum_set_of_tasks_which_overflow_capacity<'a, Var: IntegerVariable + ' .profile_tasks .iter() .take_while(move |task| { - if *resource_usage > parameters.capacity { + if *resource_usage > context.upper_bound(¶meters.capacity) { return false; } - *resource_usage += task.resource_usage; + *resource_usage += context.lower_bound(&task.resource_usage); true }) .cloned() @@ -109,11 +125,16 @@ fn get_minimum_set_of_tasks_which_overflow_capacity<'a, Var: IntegerVariable + ' /// by [`TimeTablePerPointPropagator`]), this function calculates the error which would have been /// reported by [`TimeTablePerPointPropagator`] by finding the tasks which should be included in the /// profile and sorting them in the same order. -pub(crate) fn create_synchronised_conflict_explanation( +pub(crate) fn create_synchronised_conflict_explanation< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, +>( context: PropagationContext, inference_code: InferenceCode, - conflicting_profile: &mut ResourceProfile, - parameters: &CumulativeParameters, + conflicting_profile: &mut ResourceProfile, + parameters: &CumulativeParameters, ) -> PropagationStatusCP { // Store because we are mutably borrowing the conflicting profile let new_profile_start = conflicting_profile.start; @@ -147,14 +168,25 @@ pub(crate) fn create_synchronised_conflict_explanation( - time_table: impl Iterator>, +pub(crate) fn synchronise_time_table< + 'a, + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( + time_table: impl Iterator>, ) { time_table.for_each(|profile| sort_profile_based_on_id(profile)) } /// Sorts the provided `profile` on non-decreasing order of ID -fn sort_profile_based_on_id(profile: &mut ResourceProfile) { +fn sort_profile_based_on_id< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( + profile: &mut ResourceProfile, +) { profile .profile_tasks .sort_by(|a, b| a.id.unpack().cmp(&b.id.unpack())); diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs index 88f4745ac..9618ee4fa 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs @@ -70,16 +70,22 @@ use crate::pumpkin_assert_extreme; /// Computer Science and Software Engineering, 2011. #[derive(Debug)] -pub(crate) struct TimeTablePerPointIncrementalPropagator { +pub(crate) struct TimeTablePerPointIncrementalPropagator< + Var, + PVar, + RVar, + CVar, + const SYNCHRONISE: bool, +> { /// The key `t` (representing a time-point) holds the mandatory resource consumption of /// [`Task`]s at that time (stored in a [`ResourceProfile`]); the [`ResourceProfile`]s are /// sorted based on start time and they are assumed to be non-overlapping - time_table: PerPointTimeTableType, + time_table: PerPointTimeTableType, /// Stores the input parameters to the cumulative constraint - parameters: CumulativeParameters, + parameters: CumulativeParameters, /// Stores structures which change during the search; either to store bounds or when applying /// incrementality - updatable_structures: UpdatableStructures, + updatable_structures: UpdatableStructures, /// Stores whether the propagator found a conflict in the previous call /// /// This is stored to deal with the case where the same conflict can be created via two @@ -98,8 +104,14 @@ pub(crate) struct TimeTablePerPointIncrementalPropagator, } -impl PropagatorConstructor - for TimeTablePerPointIncrementalPropagator +impl< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, + const SYNCHRONISE: bool, + > PropagatorConstructor + for TimeTablePerPointIncrementalPropagator { type PropagatorImpl = Self; @@ -117,15 +129,20 @@ impl Propagator } } -impl - TimeTablePerPointIncrementalPropagator +impl< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, + const SYNCHRONISE: bool, + > TimeTablePerPointIncrementalPropagator { pub(crate) fn new( - arg_tasks: &[ArgTask], - capacity: i32, + arg_tasks: &[ArgTask], + capacity: CVar, cumulative_options: CumulativePropagatorOptions, constraint_tag: ConstraintTag, - ) -> TimeTablePerPointIncrementalPropagator { + ) -> TimeTablePerPointIncrementalPropagator { let tasks = create_tasks(arg_tasks); let parameters = CumulativeParameters::new(tasks, capacity, cumulative_options); let updatable_structures = UpdatableStructures::new(¶meters); @@ -146,7 +163,7 @@ impl &mut self, context: PropagationContext, mandatory_part_adjustments: &MandatoryPartAdjustments, - task: &Rc>, + task: &Rc>, ) -> PropagationStatusCP { // Go over all of the updated tasks and calculate the added mandatory part (we know // that for each of these tasks, a mandatory part exists, otherwise it would not @@ -160,7 +177,7 @@ impl "Attempted to insert mandatory part where it already exists at time point {time_point} for task {} in time-table per time-point propagator\n", task.id.unpack() as usize); // Add the updated profile to the ResourceProfile at time t - let current_profile: &mut ResourceProfile = self + let current_profile: &mut ResourceProfile = self .time_table .entry(time_point as u32) .or_insert(ResourceProfile::default(time_point)); @@ -192,7 +209,7 @@ impl fn remove_from_time_table( &mut self, mandatory_part_adjustments: &MandatoryPartAdjustments, - task: &Rc>, + task: &Rc>, ) { for time_point in mandatory_part_adjustments.get_removed_parts().flatten() { pumpkin_assert_extreme!( @@ -381,8 +398,13 @@ impl } } -impl Propagator - for TimeTablePerPointIncrementalPropagator +impl< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, + const SYNCHRONISE: bool, + > Propagator for TimeTablePerPointIncrementalPropagator { fn propagate(&mut self, mut context: PropagationContextMut) -> PropagationStatusCP { pumpkin_assert_advanced!( @@ -551,12 +573,15 @@ mod debug { /// the same! pub(crate) fn time_tables_are_the_same_point< Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, const SYNCHRONISE: bool, >( context: PropagationContext, inference_code: InferenceCode, - time_table: &PerPointTimeTableType, - parameters: &CumulativeParameters, + time_table: &PerPointTimeTableType, + parameters: &CumulativeParameters, ) -> bool { let time_table_scratch = create_time_table_per_point_from_scratch(context, inference_code, parameters) diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs index e19575e96..1e1ae3acb 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs @@ -66,14 +66,16 @@ impl CumulativePropagationHandler { /// Propagates the lower-bound of the `propagating_task` to not conflict with all of the /// `profiles` anymore. - pub(crate) fn propagate_chain_of_lower_bounds_with_explanations( + pub(crate) fn propagate_chain_of_lower_bounds_with_explanations( &mut self, context: &mut PropagationContextMut, - profiles: &[&ResourceProfile], - propagating_task: &Rc>, + profiles: &[&ResourceProfile], + propagating_task: &Rc>, ) -> Result<(), EmptyDomain> where Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, { pumpkin_assert_simple!(!profiles.is_empty()); match self.explanation_type { @@ -131,14 +133,16 @@ impl CumulativePropagationHandler { /// Propagates the upper-bound of the `propagating_task` to not conflict with all of the /// `profiles` anymore. - pub(crate) fn propagate_chain_of_upper_bounds_with_explanations( + pub(crate) fn propagate_chain_of_upper_bounds_with_explanations( &mut self, context: &mut PropagationContextMut, - profiles: &[&ResourceProfile], - propagating_task: &Rc>, + profiles: &[&ResourceProfile], + propagating_task: &Rc>, ) -> Result<(), EmptyDomain> where Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, { pumpkin_assert_simple!(!profiles.is_empty()); @@ -196,14 +200,16 @@ impl CumulativePropagationHandler { } /// Propagates the lower-bound of the `propagating_task` to not conflict with `profile` anymore. - pub(crate) fn propagate_lower_bound_with_explanations( + pub(crate) fn propagate_lower_bound_with_explanations( &mut self, context: &mut PropagationContextMut, - profile: &ResourceProfile, - propagating_task: &Rc>, + profile: &ResourceProfile, + propagating_task: &Rc>, ) -> Result<(), EmptyDomain> where Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, { pumpkin_assert_advanced!( context.lower_bound(&propagating_task.start_variable) < profile.end + 1 @@ -246,11 +252,11 @@ impl CumulativePropagationHandler { } /// Propagates the upper-bound of the `propagating_task` to not conflict with `profile` anymore. - pub(crate) fn propagate_upper_bound_with_explanations( + pub(crate) fn propagate_upper_bound_with_explanations( &mut self, context: &mut PropagationContextMut, - profile: &ResourceProfile, - propagating_task: &Rc>, + profile: &ResourceProfile, + propagating_task: &Rc>, ) -> Result<(), EmptyDomain> where Var: IntegerVariable + 'static, @@ -301,14 +307,16 @@ impl CumulativePropagationHandler { /// Propagates a hole in the domain; note that this explanation does not contain any of the /// bounds of `propagating_task`. - pub(crate) fn propagate_holes_in_domain( + pub(crate) fn propagate_holes_in_domain( &mut self, context: &mut PropagationContextMut, - profile: &ResourceProfile, - propagating_task: &Rc>, + profile: &ResourceProfile, + propagating_task: &Rc>, ) -> Result<(), EmptyDomain> where Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, { // We go through all of the time-points which cause `task` to overlap // with the resource profile @@ -398,13 +406,15 @@ impl CumulativePropagationHandler { } /// Either we get the stored stored profile explanation or we initialize it. - fn get_stored_profile_explanation_or_init( + fn get_stored_profile_explanation_or_init( &mut self, context: &mut PropagationContextMut, - profile: &ResourceProfile, + profile: &ResourceProfile, ) -> Rc where Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, { Rc::clone(self.stored_profile_explanation.get_or_init(|| { Rc::new( @@ -426,14 +436,16 @@ impl CumulativePropagationHandler { /// Creates an explanation of the conflict caused by `conflict_profile` based on the provided /// `explanation_type`. -pub(crate) fn create_conflict_explanation( +pub(crate) fn create_conflict_explanation( context: Context, inference_code: InferenceCode, - conflict_profile: &ResourceProfile, + conflict_profile: &ResourceProfile, explanation_type: CumulativeExplanationType, ) -> PropagatorConflict where Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, { let conjunction = match explanation_type { CumulativeExplanationType::Naive => { diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_over_interval.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_over_interval.rs index 9909a3c71..42aaf17de 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_over_interval.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_over_interval.rs @@ -39,14 +39,14 @@ use crate::pumpkin_assert_simple; /// An event storing the start and end of mandatory parts used for creating the time-table #[derive(Debug)] -pub(crate) struct Event { +pub(crate) struct Event { /// The time-point at which the [`Event`] took place time_stamp: i32, /// Change in resource usage at [time_stamp][Event::time_stamp], positive if it is the start of /// a mandatory part and negative otherwise change_in_resource_usage: i32, /// The [`Task`] which has caused the event to take place - task: Rc>, + task: Rc>, } /// [`Propagator`] responsible for using time-table reasoning to propagate the [Cumulative](https://sofdem.github.io/gccat/gccat/Ccumulative.html) constraint @@ -60,13 +60,13 @@ pub(crate) struct Event { /// \[1\] A. Schutt, Improving scheduling by learning. University of Melbourne, Department of /// Computer Science and Software Engineering, 2011. #[derive(Debug)] -pub(crate) struct TimeTableOverIntervalPropagator { +pub(crate) struct TimeTableOverIntervalPropagator { /// Stores whether the time-table is empty is_time_table_empty: bool, /// Stores the input parameters to the cumulative constraint - parameters: CumulativeParameters, + parameters: CumulativeParameters, /// Stores structures which change during the search; used to store the bounds - updatable_structures: UpdatableStructures, + updatable_structures: UpdatableStructures, // TODO: Update with propagator constructor. constraint_tag: ConstraintTag, @@ -77,15 +77,21 @@ pub(crate) struct TimeTableOverIntervalPropagator { /// /// The [ResourceProfile]s are sorted based on start time and they are non-overlapping; each entry /// in the [`Vec`] represents the mandatory resource usage across an interval. -pub(crate) type OverIntervalTimeTableType = Vec>; - -impl TimeTableOverIntervalPropagator { +pub(crate) type OverIntervalTimeTableType = Vec>; + +impl< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, + > TimeTableOverIntervalPropagator +{ pub(crate) fn new( - arg_tasks: &[ArgTask], - capacity: i32, + arg_tasks: &[ArgTask], + capacity: CVar, cumulative_options: CumulativePropagatorOptions, constraint_tag: ConstraintTag, - ) -> TimeTableOverIntervalPropagator { + ) -> TimeTableOverIntervalPropagator { let tasks = create_tasks(arg_tasks); let parameters = CumulativeParameters::new(tasks, capacity, cumulative_options); let updatable_structures = UpdatableStructures::new(¶meters); @@ -100,8 +106,12 @@ impl TimeTableOverIntervalPropagator { } } -impl PropagatorConstructor - for TimeTableOverIntervalPropagator +impl< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, + > PropagatorConstructor for TimeTableOverIntervalPropagator { type PropagatorImpl = Self; @@ -116,7 +126,13 @@ impl PropagatorConstructor } } -impl Propagator for TimeTableOverIntervalPropagator { +impl< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, + > Propagator for TimeTableOverIntervalPropagator +{ fn propagate(&mut self, mut context: PropagationContextMut) -> PropagationStatusCP { if self.parameters.is_infeasible { return Err(Inconsistency::Conflict(PropagatorConflict { @@ -216,12 +232,15 @@ impl Propagator for TimeTableOverIntervalPropaga /// conflict in the form of an [`Inconsistency`]. pub(crate) fn create_time_table_over_interval_from_scratch< Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, Context: ReadDomains + Copy, >( context: Context, - parameters: &CumulativeParameters, + parameters: &CumulativeParameters, inference_code: InferenceCode, -) -> Result, PropagatorConflict> { +) -> Result, PropagatorConflict> { // First we create a list of all the events (i.e. start and ends of mandatory parts) let events = create_events(context, parameters); @@ -236,12 +255,18 @@ pub(crate) fn create_time_table_over_interval_from_scratch< /// is resolved by placing the events which signify the ends of mandatory parts first (if the /// tie is between events of the same type then the tie-breaking is done on the id in /// non-decreasing order). -fn create_events( +fn create_events< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, + Context: ReadDomains + Copy, +>( context: Context, - parameters: &CumulativeParameters, -) -> Vec> { + parameters: &CumulativeParameters, +) -> Vec> { // First we create a list of events with which we will create the time-table - let mut events: Vec> = Vec::new(); + let mut events: Vec> = Vec::new(); // Then we go over every task for task in parameters.tasks.iter() { let upper_bound = context.upper_bound(&task.start_variable); @@ -296,12 +321,18 @@ fn create_events( /// Creates a time-table based on the provided `events` (which are assumed to be sorted /// chronologically, with tie-breaking performed in such a way that the ends of mandatory parts /// are before the starts of mandatory parts). -fn create_time_table_from_events( - events: Vec>, +fn create_time_table_from_events< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, + Context: ReadDomains + Copy, +>( + events: Vec>, context: Context, inference_code: InferenceCode, - parameters: &CumulativeParameters, -) -> Result, PropagatorConflict> { + parameters: &CumulativeParameters, +) -> Result, PropagatorConflict> { pumpkin_assert_extreme!( events.is_empty() || (0..events.len() - 1) @@ -317,9 +348,9 @@ fn create_time_table_from_events = Default::default(); + let mut time_table: OverIntervalTimeTableType = Default::default(); // The tasks which are contributing to the current profile under consideration - let mut current_profile_tasks: Vec>> = Vec::new(); + let mut current_profile_tasks: Vec>> = Vec::new(); // The cumulative resource usage of the tasks which are contributing to the current profile // under consideration let mut current_resource_usage: i32 = 0; @@ -428,10 +459,14 @@ fn create_time_table_from_events( - event: &Event, +fn check_starting_new_profile_invariants< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( + event: &Event, current_resource_usage: i32, - current_profile_tasks: &[Rc>], + current_profile_tasks: &[Rc>], ) -> bool { if event.change_in_resource_usage <= 0 { eprintln!("The resource usage of an event which causes a new profile to be started should never be negative") @@ -447,10 +482,15 @@ fn check_starting_new_profile_invariants( && current_profile_tasks.is_empty() } -pub(crate) fn debug_propagate_from_scratch_time_table_interval( +pub(crate) fn debug_propagate_from_scratch_time_table_interval< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, +>( context: &mut PropagationContextMut, - parameters: &CumulativeParameters, - updatable_structures: &UpdatableStructures, + parameters: &CumulativeParameters, + updatable_structures: &UpdatableStructures, inference_code: InferenceCode, ) -> PropagationStatusCP { // We first create a time-table over interval and return an error if there was diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_per_point.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_per_point.rs index ba9ff539d..0ef81ce55 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_per_point.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_per_point.rs @@ -51,13 +51,13 @@ use crate::pumpkin_assert_extreme; /// Computer Science and Software Engineering, 2011. #[derive(Debug)] -pub(crate) struct TimeTablePerPointPropagator { +pub(crate) struct TimeTablePerPointPropagator { /// Stores whether the time-table is empty is_time_table_empty: bool, /// Stores the input parameters to the cumulative constraint - parameters: CumulativeParameters, + parameters: CumulativeParameters, /// Stores structures which change during the search; used to store the bounds - updatable_structures: UpdatableStructures, + updatable_structures: UpdatableStructures, // TODO: Update with proapgator constructor. constraint_tag: ConstraintTag, @@ -71,15 +71,22 @@ pub(crate) struct TimeTablePerPointPropagator { /// The key t (representing a time-point) holds the mandatory resource consumption of tasks at /// that time (stored in a [`ResourceProfile`]); the [ResourceProfile]s are sorted based on /// start time and they are non-overlapping -pub(crate) type PerPointTimeTableType = BTreeMap>; - -impl TimeTablePerPointPropagator { +pub(crate) type PerPointTimeTableType = + BTreeMap>; + +impl< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, + > TimeTablePerPointPropagator +{ pub(crate) fn new( - arg_tasks: &[ArgTask], - capacity: i32, + arg_tasks: &[ArgTask], + capacity: CVar, cumulative_options: CumulativePropagatorOptions, constraint_tag: ConstraintTag, - ) -> TimeTablePerPointPropagator { + ) -> TimeTablePerPointPropagator { let tasks = create_tasks(arg_tasks); let parameters = CumulativeParameters::new(tasks, capacity, cumulative_options); let updatable_structures = UpdatableStructures::new(¶meters); @@ -94,7 +101,13 @@ impl TimeTablePerPointPropagator { } } -impl PropagatorConstructor for TimeTablePerPointPropagator { +impl< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, + > PropagatorConstructor for TimeTablePerPointPropagator +{ type PropagatorImpl = Self; fn create(mut self, mut context: PropagatorConstructorContext) -> Self::PropagatorImpl { @@ -108,7 +121,13 @@ impl PropagatorConstructor for TimeTablePerPoint } } -impl Propagator for TimeTablePerPointPropagator { +impl< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, + > Propagator for TimeTablePerPointPropagator +{ fn propagate(&mut self, mut context: PropagationContextMut) -> PropagationStatusCP { if self.parameters.is_infeasible { return Err(Inconsistency::Conflict(PropagatorConflict { @@ -208,13 +227,16 @@ impl Propagator for TimeTablePerPointPropagator< /// conflict in the form of an [`Inconsistency`]. pub(crate) fn create_time_table_per_point_from_scratch< Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, Context: ReadDomains + Copy, >( context: Context, inference_code: InferenceCode, - parameters: &CumulativeParameters, -) -> Result, PropagatorConflict> { - let mut time_table: PerPointTimeTableType = PerPointTimeTableType::new(); + parameters: &CumulativeParameters, +) -> Result, PropagatorConflict> { + let mut time_table: PerPointTimeTableType = PerPointTimeTableType::new(); // First we go over all tasks and determine their mandatory parts for task in parameters.tasks.iter() { let upper_bound = context.upper_bound(&task.start_variable); @@ -226,7 +248,7 @@ pub(crate) fn create_time_table_per_point_from_scratch< // For every time-point of the mandatory part, // add the resource usage of the current task to the ResourceProfile and add it // to the profile tasks of the resource - let current_profile: &mut ResourceProfile = time_table + let current_profile: &mut ResourceProfile = time_table .entry(i as u32) .or_insert(ResourceProfile::default(i)); current_profile.height += task.resource_usage; @@ -254,11 +276,16 @@ pub(crate) fn create_time_table_per_point_from_scratch< Ok(time_table) } -pub(crate) fn debug_propagate_from_scratch_time_table_point( +pub(crate) fn debug_propagate_from_scratch_time_table_point< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, +>( context: &mut PropagationContextMut, inference_code: InferenceCode, - parameters: &CumulativeParameters, - updatable_structures: &UpdatableStructures, + parameters: &CumulativeParameters, + updatable_structures: &UpdatableStructures, ) -> PropagationStatusCP { // We first create a time-table per point and return an error if there was // an overflow of the resource capacity while building the time-table diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_util.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_util.rs index 51aa11918..6887b7855 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_util.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_util.rs @@ -26,7 +26,7 @@ use crate::pumpkin_assert_moderate; /// The result of [`should_enqueue`], contains the [`EnqueueDecision`] whether the propagator should /// currently be enqueued and potentially the updated [`Task`] (in the form of a /// [`UpdatedTaskInfo`]) if the mandatory part of this [`Task`] has changed. -pub(crate) struct ShouldEnqueueResult { +pub(crate) struct ShouldEnqueueResult { /// Whether the propagator which called this method should be enqueued pub(crate) decision: EnqueueDecision, /// If the mandatory part of the task passed to [`should_enqueue`] has changed then this field @@ -34,20 +34,25 @@ pub(crate) struct ShouldEnqueueResult { /// /// In general, non-incremental propagators will not make use of this field since they will /// propagate from scratch anyways. - pub(crate) update: Option>, + pub(crate) update: Option>, } /// Determines whether a time-table propagator should enqueue and returns a structure containing the /// [`EnqueueDecision`] and the info of the task with the extended mandatory part (or [`None`] if no /// such task exists). This method should be called in the /// [`ConstraintProgrammingPropagator::notify`] method. -pub(crate) fn should_enqueue( - parameters: &CumulativeParameters, - updatable_structures: &UpdatableStructures, - updated_task: &Rc>, +pub(crate) fn should_enqueue< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, +>( + parameters: &CumulativeParameters, + updatable_structures: &UpdatableStructures, + updated_task: &Rc>, context: PropagationContext, empty_time_table: bool, -) -> ShouldEnqueueResult { +) -> ShouldEnqueueResult { 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) @@ -105,9 +110,13 @@ pub(crate) fn should_enqueue( result } -pub(crate) fn has_mandatory_part( +pub(crate) fn has_mandatory_part< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( context: PropagationContext, - task: &Rc>, + task: &Rc>, ) -> bool { context.upper_bound(&task.start_variable) < context.lower_bound(&task.start_variable) + task.processing_time @@ -115,9 +124,13 @@ pub(crate) fn has_mandatory_part( /// Checks whether a specific task (indicated by id) has a mandatory part which overlaps with the /// interval [start, end] -pub(crate) fn has_mandatory_part_in_interval( +pub(crate) fn has_mandatory_part_in_interval< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( context: PropagationContext, - task: &Rc>, + task: &Rc>, start: i32, end: i32, ) -> bool { @@ -132,9 +145,13 @@ pub(crate) fn has_mandatory_part_in_interval( } /// Checks whether the lower and upper bound of a task overlap with the provided interval -pub(crate) fn task_has_overlap_with_interval( +pub(crate) fn task_has_overlap_with_interval< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( context: PropagationContext, - task: &Rc>, + task: &Rc>, start: i32, end: i32, ) -> bool { @@ -160,8 +177,13 @@ pub(crate) fn has_overlap_with_interval( /// based on start time and that the profiles are maximal (i.e. the [`ResourceProfile::start`] and /// [`ResourceProfile::end`] cannot be increased or decreased, respectively). It returns true if /// both of these invariants hold and false otherwise. -fn debug_check_whether_profiles_are_maximal_and_sorted<'a, Var: IntegerVariable + 'static>( - time_table: impl Iterator> + Clone, +fn debug_check_whether_profiles_are_maximal_and_sorted< + 'a, + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( + time_table: impl Iterator> + Clone, ) -> bool { let collected_time_table = time_table.clone().collect::>(); let sorted_profiles = collected_time_table.is_empty() @@ -199,12 +221,18 @@ fn debug_check_whether_profiles_are_maximal_and_sorted<'a, Var: IntegerVariable /// sorted in increasing order in terms of [`ResourceProfile::start`] and that the /// [`ResourceProfile`] is maximal (i.e. the [`ResourceProfile::start`] and [`ResourceProfile::end`] /// cannot be increased or decreased, respectively). -pub(crate) fn propagate_based_on_timetable<'a, Var: IntegerVariable + 'static>( +pub(crate) fn propagate_based_on_timetable< + 'a, + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, +>( context: &mut PropagationContextMut, inference_code: InferenceCode, - time_table: impl Iterator> + Clone, - parameters: &CumulativeParameters, - updatable_structures: &mut UpdatableStructures, + time_table: impl Iterator> + Clone, + parameters: &CumulativeParameters, + updatable_structures: &mut UpdatableStructures, ) -> PropagationStatusCP { pumpkin_assert_extreme!( debug_check_whether_profiles_are_maximal_and_sorted(time_table.clone()), @@ -254,12 +282,18 @@ pub(crate) fn propagate_based_on_timetable<'a, Var: IntegerVariable + 'static>( /// /// This type of propagation is likely to be less beneficial for the explanation /// [`CumulativeExplanationType::Pointwise`]. -fn propagate_single_profiles<'a, Var: IntegerVariable + 'static>( +fn propagate_single_profiles< + 'a, + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, +>( context: &mut PropagationContextMut, inference_code: InferenceCode, - time_table: impl Iterator> + Clone, - updatable_structures: &mut UpdatableStructures, - parameters: &CumulativeParameters, + time_table: impl Iterator> + Clone, + updatable_structures: &mut UpdatableStructures, + parameters: &CumulativeParameters, ) -> PropagationStatusCP { // We create the structure responsible for propagations and explanations let mut propagation_handler = @@ -338,12 +372,18 @@ fn propagate_single_profiles<'a, Var: IntegerVariable + 'static>( /// /// Especially in the case of [`CumulativeExplanationType::Pointwise`] this is likely to be /// beneficial. -fn propagate_sequence_of_profiles<'a, Var: IntegerVariable + 'static>( +fn propagate_sequence_of_profiles< + 'a, + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, +>( context: &mut PropagationContextMut, inference_code: InferenceCode, - time_table: impl Iterator> + Clone, - updatable_structures: &UpdatableStructures, - parameters: &CumulativeParameters, + time_table: impl Iterator> + Clone, + updatable_structures: &UpdatableStructures, + parameters: &CumulativeParameters, ) -> PropagationStatusCP { // We create the structure responsible for propagations and explanations let mut propagation_handler = @@ -462,11 +502,15 @@ fn propagate_sequence_of_profiles<'a, Var: IntegerVariable + 'static>( /// Returns the index of the profile which cannot propagate the lower-bound of the provided task any /// further based on the propagation of the upper-bound due to `time_table[profile_index]`. -fn find_index_last_profile_which_propagates_lower_bound( +fn find_index_last_profile_which_propagates_lower_bound< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( profile_index: usize, - time_table: &[&ResourceProfile], + time_table: &[&ResourceProfile], context: PropagationContext, - task: &Rc>, + task: &Rc>, capacity: i32, ) -> usize { let mut last_index = profile_index + 1; @@ -484,11 +528,15 @@ fn find_index_last_profile_which_propagates_lower_bound( +fn find_index_last_profile_which_propagates_upper_bound< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( profile_index: usize, - time_table: &[&ResourceProfile], + time_table: &[&ResourceProfile], context: PropagationContext, - task: &Rc>, + task: &Rc>, capacity: i32, ) -> usize { if profile_index == 0 { @@ -526,10 +574,14 @@ fn find_index_last_profile_which_propagates_upper_bound capacity (i.e. the task has the /// potential to overflow the capacity in combination with the profile) -fn lower_bound_can_be_propagated_by_profile( +fn lower_bound_can_be_propagated_by_profile< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( context: PropagationContext, - task: &Rc>, - profile: &ResourceProfile, + task: &Rc>, + profile: &ResourceProfile, capacity: i32, ) -> bool { pumpkin_assert_moderate!( @@ -546,10 +598,14 @@ fn lower_bound_can_be_propagated_by_profile( /// [`ResourceProfile`] /// * ub(s) <= end, i.e. the latest start time is before the end of the [`ResourceProfile`] /// Note: It is assumed that the task is known to overflow the [`ResourceProfile`] -fn upper_bound_can_be_propagated_by_profile( +fn upper_bound_can_be_propagated_by_profile< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( context: PropagationContext, - task: &Rc>, - profile: &ResourceProfile, + task: &Rc>, + profile: &ResourceProfile, capacity: i32, ) -> bool { pumpkin_assert_moderate!( @@ -566,10 +622,14 @@ fn upper_bound_can_be_propagated_by_profile( /// /// If the first condition is true, the second false and the third true then this method returns /// true (otherwise it returns false) -fn can_be_updated_by_profile( +fn can_be_updated_by_profile< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( context: PropagationContext, - task: &Rc>, - profile: &ResourceProfile, + task: &Rc>, + profile: &ResourceProfile, capacity: i32, ) -> bool { overflows_capacity_and_is_not_part_of_profile(context, task, profile, capacity) @@ -582,10 +642,14 @@ fn can_be_updated_by_profile( /// /// If the first condition is true, and the second false then this method returns /// true (otherwise it returns false) -fn overflows_capacity_and_is_not_part_of_profile( +fn overflows_capacity_and_is_not_part_of_profile< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( context: PropagationContext, - task: &Rc>, - profile: &ResourceProfile, + task: &Rc>, + profile: &ResourceProfile, capacity: i32, ) -> bool { profile.height + task.resource_usage > capacity @@ -605,11 +669,16 @@ enum CanUpdate { /// /// Note that this method can only find [`Inconsistency::EmptyDomain`] conflicts which means that we /// handle that error in the parent function -fn find_possible_updates( +fn find_possible_updates< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, +>( context: &mut PropagationContextMut, - task: &Rc>, - profile: &ResourceProfile, - parameters: &CumulativeParameters, + task: &Rc>, + profile: &ResourceProfile, + parameters: &CumulativeParameters, ) -> Vec { if !can_be_updated_by_profile(context.as_readonly(), task, profile, parameters.capacity) { // If the task cannot be updated by the profile then we simply return the empty list @@ -644,10 +713,14 @@ fn find_possible_updates( } } -pub(crate) fn insert_update( - updated_task: &Rc>, - updatable_structures: &mut UpdatableStructures, - potential_update: Option>, +pub(crate) fn insert_update< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( + updated_task: &Rc>, + updatable_structures: &mut UpdatableStructures, + potential_update: Option>, ) { if let Some(update) = potential_update { updatable_structures.task_has_been_updated(updated_task); @@ -655,10 +728,14 @@ pub(crate) fn insert_update( } } -pub(crate) fn backtrack_update( +pub(crate) fn backtrack_update< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( context: PropagationContext, - updatable_structures: &mut UpdatableStructures, - updated_task: &Rc>, + updatable_structures: &mut UpdatableStructures, + updated_task: &Rc>, ) { // Stores whether the stored lower-bound is equal to the current lower-bound let lower_bound_equal_to_stored = updatable_structures.get_stored_lower_bound(updated_task) diff --git a/pumpkin-crates/core/src/propagators/cumulative/utils/structs/mandatory_part_adjustments.rs b/pumpkin-crates/core/src/propagators/cumulative/utils/structs/mandatory_part_adjustments.rs index 50136337c..cca90a9ab 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/utils/structs/mandatory_part_adjustments.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/utils/structs/mandatory_part_adjustments.rs @@ -76,7 +76,7 @@ impl MandatoryPartAdjustments { } } -impl UpdatedTaskInfo { +impl UpdatedTaskInfo { /// Returns the adjustments which need to be made to the time-table in the form of a /// [`MandatoryPartAdjustments`]. pub(crate) fn get_mandatory_part_adjustments(&self) -> MandatoryPartAdjustments { diff --git a/pumpkin-crates/core/src/propagators/cumulative/utils/structs/parameters.rs b/pumpkin-crates/core/src/propagators/cumulative/utils/structs/parameters.rs index e11075741..cc2e75cde 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/utils/structs/parameters.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/utils/structs/parameters.rs @@ -9,14 +9,14 @@ use crate::variables::IntegerVariable; /// - The capacity of the resource /// - The options for propagating the cumulative constraint #[derive(Debug, Clone)] -pub(crate) struct CumulativeParameters { +pub(crate) struct CumulativeParameters { /// The Set of [`Task`]s; for each [`Task`], the [`Task::id`] is assumed to correspond to its /// index in this [`Vec`]; this is stored as a [`Box`] of [`Rc`]'s to accomodate the /// sharing of the tasks - pub(crate) tasks: Box<[Rc>]>, + pub(crate) tasks: Box<[Rc>]>, /// The capacity of the resource (i.e. how much resource consumption can be maximally /// accomodated at each time point) - pub(crate) capacity: i32, + pub(crate) capacity: CVar, /// The [`CumulativeOptions`] which influence the behaviour of the cumulative propagator(s). pub(crate) options: CumulativePropagatorOptions, /// Indicates that the constraint is infeasible. @@ -25,12 +25,18 @@ pub(crate) struct CumulativeParameters { pub(crate) is_infeasible: bool, } -impl CumulativeParameters { +impl< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, + > CumulativeParameters +{ pub(crate) fn new( - tasks: Vec>, - capacity: i32, + tasks: Vec>, + capacity: CVar, options: CumulativePropagatorOptions, - ) -> CumulativeParameters { + ) -> CumulativeParameters { let mut is_infeasible = false; let tasks = tasks .into_iter() diff --git a/pumpkin-crates/core/src/propagators/cumulative/utils/structs/resource_profile.rs b/pumpkin-crates/core/src/propagators/cumulative/utils/structs/resource_profile.rs index fc3be6708..f2f1d1f29 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/utils/structs/resource_profile.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/utils/structs/resource_profile.rs @@ -7,31 +7,26 @@ use crate::variables::IntegerVariable; /// Structures used for storing the data related to resource profiles; /// A [`ResourceProfile`] represents a rectangle where the height is the cumulative mandatory /// resource usage of the [`profile tasks`][ResourceProfile::profile_tasks] -#[derive(Clone)] -pub(crate) struct ResourceProfile { +#[derive(Clone, Debug)] +pub(crate) struct ResourceProfile { /// The start time of the [`ResourceProfile`] (inclusive) pub(crate) start: i32, /// The end time of the [`ResourceProfile`] (inclusive) pub(crate) end: i32, /// The IDs of the tasks which are part of the profile - pub(crate) profile_tasks: Vec>>, + pub(crate) profile_tasks: Vec>>, /// The amount of cumulative resource usage of all [`profile /// tasks`][ResourceProfile::profile_tasks] (i.e. the height of the rectangle) pub(crate) height: i32, } -impl Debug for ResourceProfile { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ResourceProfile") - .field("start", &self.start) - .field("end", &self.end) - .field("height", &self.height) - .finish() - } -} - -impl ResourceProfile { - pub(crate) fn default(time: i32) -> ResourceProfile { +impl< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + > ResourceProfile +{ + pub(crate) fn default(time: i32) -> ResourceProfile { ResourceProfile { start: time, end: time, diff --git a/pumpkin-crates/core/src/propagators/cumulative/utils/structs/task.rs b/pumpkin-crates/core/src/propagators/cumulative/utils/structs/task.rs index dff382659..060df54c9 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/utils/structs/task.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/utils/structs/task.rs @@ -7,55 +7,67 @@ use crate::variables::IntegerVariable; /// Structure which stores the variables related to a task; for now, only the start times are /// assumed to be variable -pub(crate) struct Task { +#[derive(Debug)] +pub(crate) struct Task { /// The variable representing the start time of a task pub(crate) start_variable: Var, /// The processing time of the `start_variable` (also referred to as duration of a task) - pub(crate) processing_time: i32, + pub(crate) processing_time: PVar, /// How much of the resource the given task uses during its non-preemptive execution - pub(crate) resource_usage: i32, + pub(crate) resource_usage: RVar, /// The [`LocalId`] of the task pub(crate) id: LocalId, } -impl Debug for Task { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Task") - .field("processing_time", &self.processing_time) - .field("resource_usage", &self.resource_usage) - .field("local_id", &self.id) - .finish() - } -} - -impl Task { - pub(crate) fn get_id(task: &Rc>) -> usize { +impl< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + > Task +{ + pub(crate) fn get_id(task: &Rc>) -> usize { task.id.unpack() as usize } } -impl Hash for Task { +impl< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + > Hash for Task +{ fn hash(&self, state: &mut H) { self.id.hash(state); } } -impl PartialEq for Task { +impl< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + > PartialEq for Task +{ fn eq(&self, other: &Self) -> bool { self.id.unpack() == other.id.unpack() } } -impl Eq for Task {} +impl< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + > Eq for Task +{ +} /// The task which is passed as argument #[derive(Clone, Debug)] -pub(crate) struct ArgTask { +pub(crate) struct ArgTask { /// The [`IntegerVariable`] representing the start time of a task pub(crate) start_time: Var, /// The processing time of the [`start_time`][ArgTask::start_time] (also referred to as /// duration of a task) - pub(crate) processing_time: i32, + pub(crate) processing_time: PVar, /// How much of the resource the given task uses during its non-preemptive execution - pub(crate) resource_usage: i32, + pub(crate) resource_usage: RVar, } diff --git a/pumpkin-crates/core/src/propagators/cumulative/utils/structs/updatable_structures.rs b/pumpkin-crates/core/src/propagators/cumulative/utils/structs/updatable_structures.rs index 948cb9c93..67bd0fd9a 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/utils/structs/updatable_structures.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/utils/structs/updatable_structures.rs @@ -12,7 +12,7 @@ use crate::variables::IntegerVariable; /// Structures which are adjusted during search; either due to incrementality or to keep track of /// bounds. #[derive(Debug, Clone)] -pub(crate) struct UpdatableStructures { +pub(crate) struct UpdatableStructures { /// The current known bounds of the different [tasks][CumulativeParameters::tasks]; stored as /// (lower bound, upper bound) /// @@ -20,15 +20,20 @@ pub(crate) struct UpdatableStructures { bounds: Vec<(i32, i32)>, /// The [`Task`]s which have been updated since the last round of propagation, this structure /// is updated by the (incremental) propagator - updates: Vec>, + updates: Vec>, /// The tasks which have been updated since the last iteration - updated_tasks: SparseSet>>, + updated_tasks: SparseSet>>, /// The tasks which are unfixed - unfixed_tasks: SparseSet>>, + unfixed_tasks: SparseSet>>, } -impl UpdatableStructures { - pub(crate) fn new(parameters: &CumulativeParameters) -> Self { +impl< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + > UpdatableStructures +{ + pub(crate) fn new(parameters: &CumulativeParameters) -> Self { let mut updated_tasks = SparseSet::new(parameters.tasks.to_vec(), Task::get_id); updated_tasks.set_to_empty(); @@ -47,7 +52,7 @@ impl UpdatableStructures { } /// Returns the next updated task and removes it from the updated list - pub(crate) fn pop_next_updated_task(&mut self) -> Option>> { + pub(crate) fn pop_next_updated_task(&mut self) -> Option>> { if self.updated_tasks.is_empty() { return None; } @@ -60,14 +65,14 @@ impl UpdatableStructures { /// whether the updated task was actually updated). pub(crate) fn get_update_for_task( &mut self, - updated_task: &Rc>, - ) -> UpdatedTaskInfo { + updated_task: &Rc>, + ) -> UpdatedTaskInfo { self.updates[updated_task.id.unpack() as usize].clone() } /// Resets the stored update for the current task to be equal to the current scenario; i.e. /// resets the old bounds to be equal to the new bounds - pub(crate) fn reset_update_for_task(&mut self, updated_task: &Rc>) { + pub(crate) fn reset_update_for_task(&mut self, updated_task: &Rc>) { let update = &mut self.updates[updated_task.id.unpack() as usize]; update.old_lower_bound = update.new_lower_bound; @@ -85,30 +90,30 @@ impl UpdatableStructures { } /// Returns the stored lower-bound for a task. - pub(crate) fn get_stored_lower_bound(&self, task: &Rc>) -> i32 { + pub(crate) fn get_stored_lower_bound(&self, task: &Rc>) -> i32 { self.bounds[task.id.unpack() as usize].0 } /// Returns the stored upper-bound for a task. - pub(crate) fn get_stored_upper_bound(&self, task: &Rc>) -> i32 { + pub(crate) fn get_stored_upper_bound(&self, task: &Rc>) -> i32 { self.bounds[task.id.unpack() as usize].1 } /// Fixes a task in the internal structure(s). - pub(crate) fn fix_task(&mut self, updated_task: &Rc>) { + pub(crate) fn fix_task(&mut self, updated_task: &Rc>) { self.unfixed_tasks.remove(updated_task); } /// Unfixes a task in the internal structure(s). - pub(crate) fn unfix_task(&mut self, updated_task: Rc>) { + pub(crate) fn unfix_task(&mut self, updated_task: Rc>) { self.unfixed_tasks.insert(updated_task); } /// Removes the fixed tasks from the internal structure(s). - pub(crate) fn remove_fixed( + pub(crate) fn remove_fixed( &mut self, context: PropagationContext, - parameters: &CumulativeParameters, + parameters: &CumulativeParameters, ) { for task in parameters.tasks.iter() { // If the task is fixed then we remove it, otherwise we insert it as an unfixed task @@ -122,10 +127,10 @@ impl UpdatableStructures { /// Resets all of the bounds to the current values in the context and removes all of the fixed /// tasks from the internal structure(s). - pub(crate) fn reset_all_bounds_and_remove_fixed( + pub(crate) fn reset_all_bounds_and_remove_fixed( &mut self, context: PropagationContext, - parameters: &CumulativeParameters, + parameters: &CumulativeParameters, ) { for task in parameters.tasks.iter() { if self.updates.len() <= task.id.unpack() as usize { @@ -172,10 +177,10 @@ impl UpdatableStructures { } // Initialises all stored bounds to their current values and removes any tasks which are fixed - pub(crate) fn initialise_bounds_and_remove_fixed( + pub(crate) fn initialise_bounds_and_remove_fixed( &mut self, context: PropagationContext, - parameters: &CumulativeParameters, + parameters: &CumulativeParameters, ) { for task in parameters.tasks.iter() { self.bounds.push(( @@ -189,12 +194,12 @@ impl UpdatableStructures { } /// Returns all of the tasks which are not currently fixed - pub(crate) fn get_unfixed_tasks(&self) -> impl Iterator>> { + pub(crate) fn get_unfixed_tasks(&self) -> impl Iterator>> { self.unfixed_tasks.iter() } // Returns all of the tasks which are currently fixed - pub(crate) fn get_fixed_tasks(&self) -> impl Iterator>> { + pub(crate) fn get_fixed_tasks(&self) -> impl Iterator>> { self.unfixed_tasks.out_of_domain() } @@ -209,7 +214,10 @@ impl UpdatableStructures { } // Temporarily removes a task from the set of unfixed tasks - pub(crate) fn temporarily_remove_task_from_unfixed(&mut self, task: &Rc>) { + pub(crate) fn temporarily_remove_task_from_unfixed( + &mut self, + task: &Rc>, + ) { self.unfixed_tasks.remove_temporarily(task) } @@ -219,12 +227,12 @@ impl UpdatableStructures { } // Returns the unfixed task at the specified index - pub(crate) fn get_unfixed_task_at_index(&self, index: usize) -> Rc> { + pub(crate) fn get_unfixed_task_at_index(&self, index: usize) -> Rc> { Rc::clone(self.unfixed_tasks.get(index)) } // Marks a task as updated in the internal structure(s) - pub(crate) fn task_has_been_updated(&mut self, task: &Rc>) { + pub(crate) fn task_has_been_updated(&mut self, task: &Rc>) { self.updated_tasks.insert(Rc::clone(task)) } @@ -232,8 +240,8 @@ impl UpdatableStructures { // are updated to the ones provided in the update pub(crate) fn insert_update_for_task( &mut self, - task: &Rc>, - updated_task_info: UpdatedTaskInfo, + task: &Rc>, + updated_task_info: UpdatedTaskInfo, ) { let stored_updated_task_info = &mut self.updates[task.id.unpack() as usize]; @@ -242,10 +250,10 @@ impl UpdatableStructures { } /// Used for creating the dynamic structures from the provided context - pub(crate) fn recreate_from_context( + pub(crate) fn recreate_from_context( &self, context: PropagationContext, - parameters: &CumulativeParameters, + parameters: &CumulativeParameters, ) -> Self { let mut other = self.clone(); diff --git a/pumpkin-crates/core/src/propagators/cumulative/utils/structs/updated_task_info.rs b/pumpkin-crates/core/src/propagators/cumulative/utils/structs/updated_task_info.rs index d354447ff..59b2833d9 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/utils/structs/updated_task_info.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/utils/structs/updated_task_info.rs @@ -5,10 +5,10 @@ use super::Task; /// Stores the information of an updated task; for example in the context of /// [`TimeTablePerPointPropagator`] this is a task whose mandatory part has changed. #[derive(Debug, Clone)] -pub(crate) struct UpdatedTaskInfo { +pub(crate) struct UpdatedTaskInfo { /// The task which has been updated (where "updated" is according to some context-dependent /// definition) - pub(crate) task: Rc>, + pub(crate) task: Rc>, /// The lower-bound of the [`Task`] before the update pub(crate) old_lower_bound: i32, /// The upper-bound of the [`Task`] before the update diff --git a/pumpkin-crates/core/src/propagators/cumulative/utils/util.rs b/pumpkin-crates/core/src/propagators/cumulative/utils/util.rs index b17ed955e..60acc5435 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/utils/util.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/utils/util.rs @@ -19,9 +19,13 @@ use crate::propagators::Task; /// registered for [`DomainEvents`]. /// /// It sorts [`Task`]s on non-decreasing resource usage and removes [`Task`]s with resource usage 0. -pub(crate) fn create_tasks( - arg_tasks: &[ArgTask], -) -> Vec> { +pub(crate) fn create_tasks< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( + arg_tasks: &[ArgTask], +) -> Vec> { // We order the tasks by non-decreasing resource usage, this allows certain optimizations let mut ordered_tasks = arg_tasks.to_vec(); ordered_tasks.sort_by(|a, b| b.resource_usage.cmp(&a.resource_usage)); @@ -45,11 +49,15 @@ pub(crate) fn create_tasks( None } }) - .collect::>>() + .collect::>>() } -pub(crate) fn register_tasks( - tasks: &[Rc>], +pub(crate) fn register_tasks< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( + tasks: &[Rc>], mut context: PropagatorConstructorContext<'_>, register_backtrack: bool, ) { @@ -75,10 +83,14 @@ pub(crate) fn register_tasks( /// Updates the bounds of the provided [`Task`] to those stored in /// `context`. -pub(crate) fn update_bounds_task( +pub(crate) fn update_bounds_task< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( context: PropagationContext, bounds: &mut [(i32, i32)], - task: &Rc>, + task: &Rc>, ) { bounds[task.id.unpack() as usize] = ( context.lower_bound(&task.start_variable), @@ -87,9 +99,13 @@ pub(crate) fn update_bounds_task( } /// Determines whether the stored bounds are equal when propagation occurs -pub(crate) fn check_bounds_equal_at_propagation( +pub(crate) fn check_bounds_equal_at_propagation< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, +>( context: PropagationContext, - tasks: &[Rc>], + tasks: &[Rc>], bounds: &[(i32, i32)], ) -> bool { tasks.iter().all(|current| { From d04fff2bc6874a9344126564e695dab94430313d Mon Sep 17 00:00:00 2001 From: Imko Marijnissen Date: Tue, 16 Sep 2025 11:16:20 +0900 Subject: [PATCH 02/17] feat: initial refactor --- pumpkin-crates/core/src/api/mod.rs | 12 +- pumpkin-crates/core/src/api/solver.rs | 4 + .../core/src/constraints/cumulative.rs | 104 +- .../core/src/engine/variables/constant.rs | 128 ++ .../core/src/engine/variables/mod.rs | 1 + .../core/src/propagators/cumulative/mod.rs | 1 + .../time_table/explanations/big_step.rs | 17 +- .../cumulative/time_table/explanations/mod.rs | 8 +- .../time_table/explanations/naive.rs | 7 +- .../time_table/explanations/pointwise.rs | 30 +- .../checks.rs | 24 +- .../debug.rs | 1 - .../insertion.rs | 15 +- .../removal.rs | 13 +- .../synchronisation.rs | 8 +- .../time_table_over_interval_incremental.rs | 772 +++++----- .../synchronisation.rs | 33 +- .../time_table_per_point_incremental.rs | 1308 +++++++++-------- .../time_table/propagation_handler.rs | 29 +- .../time_table/time_table_over_interval.rs | 160 +- .../time_table/time_table_per_point.rs | 152 +- .../cumulative/time_table/time_table_util.rs | 69 +- .../src/propagators/cumulative/utils/mod.rs | 1 + .../structs/mandatory_part_adjustments.rs | 19 +- .../cumulative/utils/structs/mod.rs | 1 + .../cumulative/utils/structs/parameters.rs | 8 +- .../cumulative/utils/structs/task.rs | 8 +- .../utils/structs/updatable_structures.rs | 7 + .../src/propagators/cumulative/utils/util.rs | 9 +- pumpkin-crates/core/src/propagators/mod.rs | 1 + pumpkin-solver-py/src/constraints/globals.rs | 104 +- .../flatzinc/compiler/post_constraints.rs | 16 +- .../src/bin/pumpkin-solver/flatzinc/mod.rs | 2 +- pumpkin-solver/src/bin/pumpkin-solver/main.rs | 3 + 34 files changed, 1858 insertions(+), 1217 deletions(-) create mode 100644 pumpkin-crates/core/src/engine/variables/constant.rs diff --git a/pumpkin-crates/core/src/api/mod.rs b/pumpkin-crates/core/src/api/mod.rs index f4767a2bc..eec3a8867 100644 --- a/pumpkin-crates/core/src/api/mod.rs +++ b/pumpkin-crates/core/src/api/mod.rs @@ -55,6 +55,15 @@ pub mod variables { use crate::Solver; } +pub mod constraint_arguments { + //! Contains inputs to constraints. + + pub use crate::propagators::ArgTask; + pub use crate::propagators::CumulativeExplanationType; + pub use crate::propagators::CumulativeOptions; + pub use crate::propagators::CumulativePropagationMethod; +} + pub mod options { //! Contains the options which can be passed to the [`Solver`]. //! @@ -67,9 +76,6 @@ pub mod options { pub use crate::engine::RestartOptions; pub use crate::engine::SatisfactionSolverOptions as SolverOptions; pub use crate::propagators::nogoods::LearningOptions; - pub use crate::propagators::CumulativeExplanationType; - pub use crate::propagators::CumulativeOptions; - pub use crate::propagators::CumulativePropagationMethod; #[cfg(doc)] use crate::Solver; } diff --git a/pumpkin-crates/core/src/api/solver.rs b/pumpkin-crates/core/src/api/solver.rs index 14e89007c..7619f1345 100644 --- a/pumpkin-crates/core/src/api/solver.rs +++ b/pumpkin-crates/core/src/api/solver.rs @@ -166,6 +166,10 @@ impl Solver { pub fn upper_bound(&self, variable: &impl IntegerVariable) -> i32 { self.satisfaction_solver.get_upper_bound(variable) } + + pub(crate) fn is_fixed(&self, variable: &impl IntegerVariable) -> bool { + self.lower_bound(variable) == self.upper_bound(variable) + } } /// Functions to create and retrieve integer and propositional variables. diff --git a/pumpkin-crates/core/src/constraints/cumulative.rs b/pumpkin-crates/core/src/constraints/cumulative.rs index 81ed53f20..6841f56c8 100644 --- a/pumpkin-crates/core/src/constraints/cumulative.rs +++ b/pumpkin-crates/core/src/constraints/cumulative.rs @@ -1,15 +1,14 @@ -use std::fmt::Debug; +use log::info; use super::Constraint; -use crate::options::CumulativePropagationMethod; +use crate::constraint_arguments::CumulativePropagationMethod; use crate::proof::ConstraintTag; use crate::propagators::ArgTask; use crate::propagators::CumulativeOptions; -use crate::propagators::TimeTableOverIntervalIncrementalPropagator; -use crate::propagators::TimeTableOverIntervalPropagator; -use crate::propagators::TimeTablePerPointIncrementalPropagator; -use crate::propagators::TimeTablePerPointPropagator; -use crate::pumpkin_assert_simple; +use crate::propagators::TimeTableOverIntervalConstructor; +use crate::propagators::TimeTableOverIntervalIncrementalConstructor; +use crate::propagators::TimeTablePerPointConstructor; +use crate::propagators::TimeTablePerPointIncrementalConstructor; use crate::variables::IntegerVariable; use crate::variables::Literal; use crate::ConstraintOperationError; @@ -192,6 +191,43 @@ impl< constraint_tag, } } + + fn swap_if_variable(&mut self, solver: &Solver) { + let mut resource_usage_constant = true; + let mut duration_constant = true; + for task in self.tasks.iter() { + if !solver.is_fixed(&task.resource_usage) { + resource_usage_constant = false; + } + + if !solver.is_fixed(&task.processing_time) { + duration_constant = false; + } + } + + let is_variable = !resource_usage_constant || !duration_constant; + let result = match self.options.propagation_method { + CumulativePropagationMethod::TimeTablePerPointIncremental if is_variable => { + info!("Could not use incremental version when having either variable resource usage or duration, switching to non-incremental version"); + CumulativePropagationMethod::TimeTablePerPoint + } + CumulativePropagationMethod::TimeTablePerPointIncrementalSynchronised => { + info!("Could not use incremental version when having either variable resource usage or duration, switching to non-incremental version"); + CumulativePropagationMethod::TimeTablePerPoint + } + CumulativePropagationMethod::TimeTableOverIntervalIncremental => { + info!("Could not use incremental version when having either variable resource usage or duration, switching to non-incremental version"); + CumulativePropagationMethod::TimeTableOverInterval + } + CumulativePropagationMethod::TimeTableOverIntervalIncrementalSynchronised => { + info!("Could not use incremental version when having either variable resource usage or duration, switching to non-incremental version"); + CumulativePropagationMethod::TimeTableOverInterval + } + other => other, + }; + + self.options.propagation_method = result; + } } impl< @@ -201,10 +237,11 @@ impl< CVar: IntegerVariable + 'static, > Constraint for CumulativeConstraint { - fn post(self, solver: &mut Solver) -> Result<(), ConstraintOperationError> { + fn post(mut self, solver: &mut Solver) -> Result<(), ConstraintOperationError> { + self.swap_if_variable(solver); match self.options.propagation_method { - CumulativePropagationMethod::TimeTablePerPoint => TimeTablePerPointPropagator::new( - &self.tasks, + CumulativePropagationMethod::TimeTablePerPoint => TimeTablePerPointConstructor::new( + self.tasks, self.resource_capacity, self.options.propagator_options, self.constraint_tag, @@ -212,8 +249,8 @@ impl< .post(solver), CumulativePropagationMethod::TimeTablePerPointIncremental => { - TimeTablePerPointIncrementalPropagator::::new( - &self.tasks, + TimeTablePerPointIncrementalConstructor::::new( + self.tasks, self.resource_capacity, self.options.propagator_options, self.constraint_tag, @@ -221,8 +258,8 @@ impl< .post(solver) } CumulativePropagationMethod::TimeTablePerPointIncrementalSynchronised => { - TimeTablePerPointIncrementalPropagator::::new( - &self.tasks, + TimeTablePerPointIncrementalConstructor::::new( + self.tasks, self.resource_capacity, self.options.propagator_options, self.constraint_tag, @@ -230,8 +267,8 @@ impl< .post(solver) } CumulativePropagationMethod::TimeTableOverInterval => { - TimeTableOverIntervalPropagator::new( - &self.tasks, + TimeTableOverIntervalConstructor::new( + self.tasks, self.resource_capacity, self.options.propagator_options, self.constraint_tag, @@ -239,8 +276,8 @@ impl< .post(solver) } CumulativePropagationMethod::TimeTableOverIntervalIncremental => { - TimeTableOverIntervalIncrementalPropagator::::new( - &self.tasks, + TimeTableOverIntervalIncrementalConstructor::::new( + self.tasks, self.resource_capacity, self.options.propagator_options, self.constraint_tag, @@ -248,8 +285,8 @@ impl< .post(solver) } CumulativePropagationMethod::TimeTableOverIntervalIncrementalSynchronised => { - TimeTableOverIntervalIncrementalPropagator::::new( - &self.tasks, + TimeTableOverIntervalIncrementalConstructor::::new( + self.tasks, self.resource_capacity, self.options.propagator_options, self.constraint_tag, @@ -260,21 +297,22 @@ impl< } fn implied_by( - self, + mut self, solver: &mut Solver, reification_literal: Literal, ) -> Result<(), ConstraintOperationError> { + self.swap_if_variable(solver); match self.options.propagation_method { - CumulativePropagationMethod::TimeTablePerPoint => TimeTablePerPointPropagator::new( - &self.tasks, + CumulativePropagationMethod::TimeTablePerPoint => TimeTablePerPointConstructor::new( + self.tasks, self.resource_capacity, self.options.propagator_options, self.constraint_tag, ) .implied_by(solver, reification_literal), CumulativePropagationMethod::TimeTablePerPointIncremental => { - TimeTablePerPointIncrementalPropagator::::new( - &self.tasks, + TimeTablePerPointIncrementalConstructor::::new( + self.tasks, self.resource_capacity, self.options.propagator_options, self.constraint_tag, @@ -282,8 +320,8 @@ impl< .implied_by(solver, reification_literal) } CumulativePropagationMethod::TimeTablePerPointIncrementalSynchronised => { - TimeTablePerPointIncrementalPropagator::::new( - &self.tasks, + TimeTablePerPointIncrementalConstructor::::new( + self.tasks, self.resource_capacity, self.options.propagator_options, self.constraint_tag, @@ -291,8 +329,8 @@ impl< .implied_by(solver, reification_literal) } CumulativePropagationMethod::TimeTableOverInterval => { - TimeTableOverIntervalPropagator::new( - &self.tasks, + TimeTableOverIntervalConstructor::new( + self.tasks, self.resource_capacity, self.options.propagator_options, self.constraint_tag, @@ -300,8 +338,8 @@ impl< .implied_by(solver, reification_literal) } CumulativePropagationMethod::TimeTableOverIntervalIncremental => { - TimeTableOverIntervalIncrementalPropagator::::new( - &self.tasks, + TimeTableOverIntervalIncrementalConstructor::::new( + self.tasks, self.resource_capacity, self.options.propagator_options, self.constraint_tag, @@ -309,8 +347,8 @@ impl< .implied_by(solver, reification_literal) } CumulativePropagationMethod::TimeTableOverIntervalIncrementalSynchronised => { - TimeTableOverIntervalIncrementalPropagator::::new( - &self.tasks, + TimeTableOverIntervalIncrementalConstructor::::new( + self.tasks, self.resource_capacity, self.options.propagator_options, self.constraint_tag, diff --git a/pumpkin-crates/core/src/engine/variables/constant.rs b/pumpkin-crates/core/src/engine/variables/constant.rs new file mode 100644 index 000000000..725ea92cf --- /dev/null +++ b/pumpkin-crates/core/src/engine/variables/constant.rs @@ -0,0 +1,128 @@ +use crate::{ + engine::{ + notifications::{DomainEvent, OpaqueDomainEvent}, + Assignments, + }, + predicates::{Predicate, PredicateConstructor}, + variables::{IntegerVariable, TransformableVariable}, +}; + +impl IntegerVariable for i32 { + type AffineView = i32; + + fn lower_bound(&self, _assignment: &Assignments) -> i32 { + *self + } + + fn lower_bound_at_trail_position( + &self, + _assignment: &Assignments, + _trail_position: usize, + ) -> i32 { + *self + } + + fn upper_bound(&self, _assignment: &Assignments) -> i32 { + *self + } + + fn upper_bound_at_trail_position( + &self, + _assignment: &Assignments, + _trail_position: usize, + ) -> i32 { + *self + } + + fn contains(&self, _assignment: &Assignments, value: i32) -> bool { + value == *self + } + + fn contains_at_trail_position( + &self, + assignment: &Assignments, + value: i32, + _trail_position: usize, + ) -> bool { + self.contains(assignment, value) + } + + fn iterate_domain(&self, _assignment: &Assignments) -> impl Iterator { + std::iter::once(*self) + } + + fn watch_all( + &self, + _watchers: &mut crate::engine::notifications::Watchers<'_>, + _events: enumset::EnumSet, + ) { + } + + fn watch_all_backtrack( + &self, + _watchers: &mut crate::engine::notifications::Watchers<'_>, + _events: enumset::EnumSet, + ) { + } + + fn unpack_event(&self, _event: OpaqueDomainEvent) -> DomainEvent { + unreachable!("A constant should never be able to notify of an event") + } + + fn get_holes_on_current_decision_level( + &self, + _assignments: &Assignments, + ) -> impl Iterator { + std::iter::empty() + } + + fn get_holes(&self, _assignments: &Assignments) -> impl Iterator { + std::iter::empty() + } +} + +impl PredicateConstructor for i32 { + type Value = i32; + + fn lower_bound_predicate(&self, bound: Self::Value) -> Predicate { + if bound >= *self { + Predicate::trivially_true() + } else { + Predicate::trivially_false() + } + } + + fn upper_bound_predicate(&self, bound: Self::Value) -> Predicate { + if bound <= *self { + Predicate::trivially_true() + } else { + Predicate::trivially_false() + } + } + + fn equality_predicate(&self, bound: Self::Value) -> Predicate { + if bound == *self { + Predicate::trivially_true() + } else { + Predicate::trivially_false() + } + } + + fn disequality_predicate(&self, bound: Self::Value) -> Predicate { + if bound != *self { + Predicate::trivially_true() + } else { + Predicate::trivially_false() + } + } +} + +impl TransformableVariable for i32 { + fn scaled(&self, scale: i32) -> i32 { + *self * scale + } + + fn offset(&self, offset: i32) -> i32 { + *self + offset + } +} diff --git a/pumpkin-crates/core/src/engine/variables/mod.rs b/pumpkin-crates/core/src/engine/variables/mod.rs index 3c5b00bcd..42837e402 100644 --- a/pumpkin-crates/core/src/engine/variables/mod.rs +++ b/pumpkin-crates/core/src/engine/variables/mod.rs @@ -3,6 +3,7 @@ //! constraints. mod affine_view; +mod constant; mod domain_generator_iterator; mod domain_id; mod integer_variable; diff --git a/pumpkin-crates/core/src/propagators/cumulative/mod.rs b/pumpkin-crates/core/src/propagators/cumulative/mod.rs index 8f1ce41d6..db9e5d92f 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/mod.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/mod.rs @@ -122,4 +122,5 @@ mod options; pub use options::*; mod utils; +pub use utils::ArgTask; pub(crate) use utils::*; diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/big_step.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/big_step.rs index 893a2c38b..f07fc0bdb 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/big_step.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/big_step.rs @@ -17,6 +17,7 @@ pub(crate) fn create_big_step_propagation_explanation< PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, >( + context: PropagationContext, profile: &ResourceProfile, ) -> PropositionalConjunction { profile @@ -25,7 +26,8 @@ pub(crate) fn create_big_step_propagation_explanation< .flat_map(|profile_task| { [ predicate!( - profile_task.start_variable >= profile.end - profile_task.processing_time + 1 + profile_task.start_variable + >= profile.end - context.lower_bound(&profile_task.processing_time) + 1 ), predicate!(profile_task.start_variable <= profile.start), ] @@ -40,6 +42,7 @@ pub(crate) fn create_big_step_conflict_explanation< PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, >( + context: PropagationContext, conflict_profile: &ResourceProfile, ) -> PropositionalConjunction { conflict_profile @@ -49,7 +52,9 @@ pub(crate) fn create_big_step_conflict_explanation< [ predicate!( profile_task.start_variable - >= conflict_profile.end - profile_task.processing_time + 1 + >= conflict_profile.end + - context.lower_bound(&profile_task.processing_time) + + 1 ), predicate!(profile_task.start_variable <= conflict_profile.start), ] @@ -58,13 +63,17 @@ pub(crate) fn create_big_step_conflict_explanation< } pub(crate) fn create_big_step_predicate_propagating_task_lower_bound_propagation( + context: PropagationContext, task: &Rc>, profile: &ResourceProfile, ) -> Predicate where Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, { - predicate!(task.start_variable >= profile.start + 1 - task.processing_time) + predicate!( + task.start_variable >= profile.start + 1 - context.lower_bound(&task.processing_time) + ) } pub(crate) fn create_big_step_predicate_propagating_task_upper_bound_propagation( @@ -80,7 +89,7 @@ where #[cfg(test)] mod tests { - use crate::options::CumulativeExplanationType; + use crate::constraint_arguments::CumulativeExplanationType; use crate::predicate; use crate::predicates::PropositionalConjunction; use crate::propagators::cumulative::time_table::propagation_handler::test_propagation_handler::TestPropagationHandler; diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/mod.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/mod.rs index 815032e59..481710035 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/mod.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/mod.rs @@ -85,10 +85,14 @@ pub(crate) fn create_predicate_propagating_task_lower_bound_propagation< create_naive_predicate_propagating_task_lower_bound_propagation(context, task) } CumulativeExplanationType::BigStep => { - create_big_step_predicate_propagating_task_lower_bound_propagation(task, profile) + create_big_step_predicate_propagating_task_lower_bound_propagation( + context, task, profile, + ) } CumulativeExplanationType::Pointwise => { - create_pointwise_predicate_propagating_task_lower_bound_propagation(task, time_point) + create_pointwise_predicate_propagating_task_lower_bound_propagation( + context, task, time_point, + ) } } } diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/naive.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/naive.rs index 2e1679520..607e0bb1a 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/naive.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/naive.rs @@ -47,10 +47,7 @@ pub(crate) fn create_naive_conflict_explanation< >( conflict_profile: &ResourceProfile, context: Context, -) -> PropositionalConjunction -where - Var: IntegerVariable + 'static, -{ +) -> PropositionalConjunction { conflict_profile .profile_tasks .iter() @@ -91,7 +88,7 @@ where #[cfg(test)] mod tests { - use crate::options::CumulativeExplanationType; + use crate::constraint_arguments::CumulativeExplanationType; use crate::predicate; use crate::predicates::PropositionalConjunction; use crate::propagators::cumulative::time_table::propagation_handler::test_propagation_handler::TestPropagationHandler; diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/pointwise.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/pointwise.rs index 460ab316c..add650e98 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/pointwise.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/pointwise.rs @@ -1,10 +1,11 @@ use std::rc::Rc; +use crate::constraint_arguments::CumulativeExplanationType; use crate::engine::propagation::contexts::propagation_context::HasAssignments; +use crate::engine::propagation::PropagationContext; use crate::engine::propagation::PropagationContextMut; use crate::engine::propagation::ReadDomains; use crate::engine::EmptyDomain; -use crate::options::CumulativeExplanationType; use crate::predicate; use crate::predicates::Predicate; use crate::predicates::PropositionalConjunction; @@ -47,7 +48,8 @@ pub(crate) fn propagate_lower_bounds_with_pointwise_explanations< // completion time - 1 (this - 1 is necessary since the explanation uses the // predicate `[s >= t_l + 1 - p]`, and this predicate holds only if the -1 is added) let mut time_point = profiles[current_profile_index].end.min( - context.lower_bound(&propagating_task.start_variable) + propagating_task.processing_time + context.lower_bound(&propagating_task.start_variable) + + context.lower_bound(&propagating_task.processing_time) - 1, ); let mut should_exit = false; @@ -64,6 +66,7 @@ pub(crate) fn propagate_lower_bounds_with_pointwise_explanations< if time_point >= context.lower_bound(&propagating_task.start_variable) { let explanation = add_propagating_task_predicate_lower_bound( create_pointwise_propagation_explanation( + context.as_readonly(), time_point, profiles[current_profile_index], ), @@ -91,7 +94,7 @@ pub(crate) fn propagate_lower_bounds_with_pointwise_explanations< } // We place the time-point as far as possible - time_point += propagating_task.processing_time; + time_point += context.lower_bound(&propagating_task.processing_time); // Then we update the index of the current profile if appropriate if time_point > profiles[current_profile_index].end { @@ -172,11 +175,12 @@ pub(crate) fn propagate_upper_bounds_with_pointwise_explanations< profiles[current_profile_index].end ); - if time_point - propagating_task.processing_time + if time_point - context.lower_bound(&propagating_task.processing_time) < context.upper_bound(&propagating_task.start_variable) { let explanation = add_propagating_task_predicate_upper_bound( create_pointwise_propagation_explanation( + context.as_readonly(), time_point, profiles[current_profile_index], ), @@ -195,7 +199,7 @@ pub(crate) fn propagate_upper_bounds_with_pointwise_explanations< context.post( predicate![ propagating_task.start_variable - <= time_point - propagating_task.processing_time + <= time_point - context.lower_bound(&propagating_task.processing_time) ], explanation, inference_code, @@ -206,7 +210,7 @@ pub(crate) fn propagate_upper_bounds_with_pointwise_explanations< break; } - time_point -= propagating_task.processing_time; + time_point -= context.lower_bound(&propagating_task.processing_time); // Then we update the index of the current profile if appropriate if time_point < profiles[current_profile_index].start { @@ -248,6 +252,7 @@ pub(crate) fn create_pointwise_propagation_explanation< PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, >( + context: PropagationContext, time_point: i32, profile: &ResourceProfile, ) -> PropositionalConjunction { @@ -257,7 +262,8 @@ pub(crate) fn create_pointwise_propagation_explanation< .flat_map(move |profile_task| { [ predicate!( - profile_task.start_variable >= time_point + 1 - profile_task.processing_time + profile_task.start_variable + >= time_point + 1 - context.lower_bound(&profile_task.processing_time) ), predicate!(profile_task.start_variable <= time_point), ] @@ -272,6 +278,7 @@ pub(crate) fn create_pointwise_conflict_explanation< PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, >( + context: PropagationContext, conflict_profile: &ResourceProfile, ) -> PropositionalConjunction { // As stated in improving scheduling by learning, we choose the middle point; this @@ -287,7 +294,8 @@ pub(crate) fn create_pointwise_conflict_explanation< .flat_map(|profile_task| { [ predicate!( - profile_task.start_variable >= middle_point + 1 - profile_task.processing_time + profile_task.start_variable + >= middle_point + 1 - context.lower_bound(&profile_task.processing_time) ), predicate!(profile_task.start_variable <= middle_point), ] @@ -296,18 +304,20 @@ pub(crate) fn create_pointwise_conflict_explanation< } pub(crate) fn create_pointwise_predicate_propagating_task_lower_bound_propagation( + context: PropagationContext, task: &Rc>, time_point: Option, ) -> Predicate where Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, { predicate!( task.start_variable >= time_point .expect("Expected time-point to be provided to pointwise explanation creation") + 1 - - task.processing_time + - context.lower_bound(&task.processing_time) ) } @@ -327,7 +337,7 @@ where #[cfg(test)] mod tests { - use crate::options::CumulativeExplanationType; + use crate::constraint_arguments::CumulativeExplanationType; use crate::predicate; use crate::predicates::PropositionalConjunction; use crate::propagators::cumulative::time_table::propagation_handler::test_propagation_handler::TestPropagationHandler; diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/checks.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/checks.rs index 74e6aac03..0e719340f 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/checks.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/checks.rs @@ -1,10 +1,12 @@ //! Contains the checks which are done when a new mandatory part is added in the propagate method to //! determine which profiles should be added and how existing profiles should be adjusted. +use crate::engine::cp::propagation::contexts::propagation_context::ReadDomains; use std::cmp::max; use std::cmp::min; use std::ops::Range; use std::rc::Rc; +use crate::engine::propagation::PropagationContext; use crate::propagators::OverIntervalTimeTableType; use crate::propagators::ResourceProfile; use crate::propagators::Task; @@ -17,6 +19,7 @@ pub(crate) fn new_profile_before_first_profile< PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, >( + context: PropagationContext, current_index: usize, start_index: usize, update_range: &Range, @@ -34,18 +37,20 @@ pub(crate) fn new_profile_before_first_profile< end: profile.start - 1, /* Note that this profile needs to end before the start * of the current profile, hence the -1 */ profile_tasks: vec![Rc::clone(task)], - height: task.resource_usage, + height: context.lower_bound(&task.resource_usage), }) } } /// Determines whether a new profile should be inserted between the current profile (pointed to /// by `current_index`) and the previous profile. +#[allow(clippy::too_many_arguments, reason = "Should be refactored")] pub(crate) fn new_profile_between_profiles< Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, >( + context: PropagationContext, time_table: &OverIntervalTimeTableType, current_index: usize, start_index: usize, @@ -74,7 +79,7 @@ pub(crate) fn new_profile_between_profiles< start: previous_profile.end + 1, end: profile.start - 1, profile_tasks: vec![Rc::clone(task)], - height: task.resource_usage, + height: context.lower_bound(&task.resource_usage), }) } } @@ -120,12 +125,14 @@ pub(crate) fn overlap_updated_profile< Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, >( + context: PropagationContext, update_range: &Range, profile: &ResourceProfile, to_add: &mut Vec>, task: &Rc>, - capacity: i32, + capacity: CVar, ) -> Result<(), ResourceProfile> { // Now we create a new profile which consists of the part of the // profile covered by the update range @@ -153,7 +160,7 @@ pub(crate) fn overlap_updated_profile< start: new_profile_lower_bound, end: new_profile_upper_bound, profile_tasks: new_profile_tasks.clone(), - height: profile.height + task.resource_usage, + height: profile.height + context.lower_bound(&task.resource_usage), }; // We thus create a new profile consisting of the combination of @@ -162,14 +169,16 @@ pub(crate) fn overlap_updated_profile< // A sanity check, there is a new profile to create consisting // of a combination of the previous profile and the updated task - if profile.height + task.resource_usage > capacity { + if profile.height + context.lower_bound(&task.resource_usage) + > context.upper_bound(&capacity) + { // The addition of the new mandatory part to the profile // caused an overflow of the resource return Err(ResourceProfile { start: new_profile_lower_bound, end: new_profile_upper_bound, profile_tasks: new_profile_tasks, - height: profile.height + task.resource_usage, + height: profile.height + context.lower_bound(&task.resource_usage), }); } } @@ -213,6 +222,7 @@ pub(crate) fn new_part_after_last_profile< PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, >( + context: PropagationContext, current_index: usize, end_index: usize, update_range: &Range, @@ -229,7 +239,7 @@ pub(crate) fn new_part_after_last_profile< start: profile.end + 1, end: update_range.end - 1, profile_tasks: vec![Rc::clone(task)], - height: task.resource_usage, + height: context.lower_bound(&task.resource_usage), }) } } diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/debug.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/debug.rs index 89fea8dd4..4e3bc8ddb 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/debug.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/debug.rs @@ -87,7 +87,6 @@ pub(crate) fn merge_profiles< Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, - CVar: IntegerVariable + 'static, >( time_table: &mut OverIntervalTimeTableType, start_index: usize, diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/insertion.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/insertion.rs index a61c1de66..5ee988847 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/insertion.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/insertion.rs @@ -1,8 +1,10 @@ //! Contains the functions necessary for inserting the appropriate profiles into the time-table //! based on the added mandatory part. +use crate::engine::cp::propagation::contexts::propagation_context::ReadDomains; use std::ops::Range; use std::rc::Rc; +use crate::engine::propagation::PropagationContext; use crate::propagators::cumulative::time_table::over_interval_incremental_propagator::checks; use crate::propagators::OverIntervalTimeTableType; use crate::propagators::ResourceProfile; @@ -20,12 +22,13 @@ pub(crate) fn insert_profiles_overlapping_with_added_mandatory_part< RVar: IntegerVariable + 'static, CVar: IntegerVariable + 'static, >( + context: PropagationContext, time_table: &mut OverIntervalTimeTableType, start_index: usize, end_index: usize, update_range: &Range, updated_task: &Rc>, - capacity: i32, + capacity: CVar, ) -> Result<(), ResourceProfile> { let mut to_add = Vec::new(); @@ -42,6 +45,7 @@ pub(crate) fn insert_profiles_overlapping_with_added_mandatory_part< // Check whether there is a new profile before the first overlapping // profile checks::new_profile_before_first_profile( + context, current_index, start_index, update_range, @@ -53,6 +57,7 @@ pub(crate) fn insert_profiles_overlapping_with_added_mandatory_part< // Check whether there is a new profile between the current profile // and the previous profile (beginning of profile remains unchanged) checks::new_profile_between_profiles( + context, time_table, current_index, start_index, @@ -75,11 +80,12 @@ pub(crate) fn insert_profiles_overlapping_with_added_mandatory_part< // // The addition of the mandatory part can lead to an overflow let result = checks::overlap_updated_profile( + context, update_range, profile, &mut to_add, updated_task, - capacity, + capacity.clone(), ); if result.is_err() && conflict.is_none() { conflict = Some(result) @@ -96,6 +102,7 @@ pub(crate) fn insert_profiles_overlapping_with_added_mandatory_part< // Check whether there is a new profile before the last overlapping // profile checks::new_part_after_last_profile( + context, current_index, end_index, update_range, @@ -122,8 +129,8 @@ pub(crate) fn insert_profile_new_mandatory_part< Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, - CVar: IntegerVariable + 'static, >( + context: PropagationContext, time_table: &mut OverIntervalTimeTableType, index_to_insert: usize, update_range: &Range, @@ -143,7 +150,7 @@ pub(crate) fn insert_profile_new_mandatory_part< start: update_range.start, end: update_range.end - 1, profile_tasks: vec![Rc::clone(updated_task)], - height: updated_task.resource_usage, + height: context.lower_bound(&updated_task.resource_usage), }, ); } diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/removal.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/removal.rs index 221074fbd..4fda999cd 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/removal.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/removal.rs @@ -1,10 +1,12 @@ //! Contains the functions necessary for removing the appropriate profiles into the time-table //! based on the reduced mandatory part. +use crate::engine::cp::propagation::contexts::propagation_context::ReadDomains; use std::cmp::max; use std::cmp::min; use std::ops::Range; use std::rc::Rc; +use crate::engine::propagation::PropagationContext; use crate::propagators::OverIntervalTimeTableType; use crate::propagators::ResourceProfile; use crate::propagators::Task; @@ -17,8 +19,8 @@ pub(crate) fn reduce_profiles_overlapping_with_added_mandatory_part< Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, - CVar: IntegerVariable + 'static, >( + context: PropagationContext, time_table: &mut OverIntervalTimeTableType, start_index: usize, end_index: usize, @@ -40,7 +42,7 @@ pub(crate) fn reduce_profiles_overlapping_with_added_mandatory_part< // Then we need to add the updated profile due to the overlap between `profile` and // `updated_task` - overlap_updated_profile(update_range, profile, &mut to_add, updated_task); + overlap_updated_profile(context, update_range, profile, &mut to_add, updated_task); // We need to check whether the last overlapping profile was split if index == end_index { @@ -59,6 +61,7 @@ fn remove_task_from_profile< PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, >( + context: PropagationContext, updated_task: &Rc>, start: i32, end: i32, @@ -76,7 +79,7 @@ fn remove_task_from_profile< start, end, profile_tasks: updated_profile_tasks, - height: profile.height - updated_task.resource_usage, + height: profile.height - context.lower_bound(&updated_task.resource_usage), } } @@ -131,12 +134,13 @@ pub(crate) fn overlap_updated_profile< PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, >( + context: PropagationContext, update_range: &Range, profile: &ResourceProfile, to_add: &mut Vec>, updated_task: &Rc>, ) { - if profile.height - updated_task.resource_usage == 0 { + if profile.height - context.lower_bound(&updated_task.resource_usage) == 0 { // If the removal of this task results in an empty profile then we simply do not add it return; } @@ -166,6 +170,7 @@ pub(crate) fn overlap_updated_profile< // We thus create a new profile consisting of the combination of // the previous profile and the updated task under consideration to_add.push(remove_task_from_profile( + context, updated_task, new_profile_lower_bound, new_profile_upper_bound, diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/synchronisation.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/synchronisation.rs index 82ba0ffb4..364199f9a 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/synchronisation.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/synchronisation.rs @@ -26,6 +26,7 @@ pub(crate) fn find_synchronised_conflict< RVar: IntegerVariable + 'static, CVar: IntegerVariable + 'static, >( + context: PropagationContext, time_table: &mut OverIntervalTimeTableType, parameters: &CumulativeParameters, ) -> Option> { @@ -35,7 +36,7 @@ pub(crate) fn find_synchronised_conflict< let first_conflict_profile_index = time_table .iter() - .position(|profile| profile.height > parameters.capacity); + .position(|profile| profile.height > context.upper_bound(¶meters.capacity)); if let Some(mut first_conflict_profile_index) = first_conflict_profile_index { let mut new_profile = time_table[first_conflict_profile_index].clone(); @@ -113,9 +114,9 @@ pub(crate) fn create_synchronised_conflict_explanation< let mut new_profile = Vec::new(); // Now we find the tasks in the profile which together overflow the resource - while resource_usage <= parameters.capacity { + while resource_usage <= context.upper_bound(¶meters.capacity) { let task = &conflicting_profile.profile_tasks[index]; - resource_usage += task.resource_usage; + resource_usage += context.lower_bound(&task.resource_usage); new_profile.push(Rc::clone(task)); index += 1; } @@ -142,7 +143,6 @@ pub(crate) fn synchronise_time_table< Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, - CVar: IntegerVariable + 'static, >( time_table: &mut OverIntervalTimeTableType, context: PropagationContext, diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/time_table_over_interval_incremental.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/time_table_over_interval_incremental.rs index c6fd88d63..fd00b2e42 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/time_table_over_interval_incremental.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/time_table_over_interval_incremental.rs @@ -1,3 +1,4 @@ +use crate::engine::cp::propagation::contexts::propagation_context::ReadDomains; use std::fmt::Debug; use std::ops::Range; use std::rc::Rc; @@ -102,9 +103,38 @@ pub(crate) struct TimeTableOverIntervalIncrementalPropagator< /// [`CumulativePropagatorOptions::incremental_backtracking`] is set to false. is_time_table_outdated: bool, - // TODO: This should be refactored to use a propagator constructor. + inference_code: InferenceCode, +} + +pub(crate) struct TimeTableOverIntervalIncrementalConstructor< + Var, + PVar, + RVar, + CVar, + const SYNCHRONISE: bool, +> { + tasks: Vec>, + capacity: CVar, + cumulative_options: CumulativePropagatorOptions, constraint_tag: ConstraintTag, - inference_code: Option, +} + +impl + TimeTableOverIntervalIncrementalConstructor +{ + pub(crate) fn new( + arg_tasks: Vec>, + capacity: CVar, + cumulative_options: CumulativePropagatorOptions, + constraint_tag: ConstraintTag, + ) -> Self { + Self { + tasks: arg_tasks, + capacity, + cumulative_options, + constraint_tag, + } + } } impl< @@ -114,28 +144,21 @@ impl< CVar: IntegerVariable + 'static, const SYNCHRONISE: bool, > PropagatorConstructor - for TimeTableOverIntervalIncrementalPropagator + for TimeTableOverIntervalIncrementalConstructor { - type PropagatorImpl = Self; - - fn create(mut self, mut context: PropagatorConstructorContext) -> Self::PropagatorImpl { - // We only register for notifications of backtrack events if incremental backtracking is - // enabled - register_tasks( - &self.parameters.tasks, - context.reborrow(), - self.parameters.options.incremental_backtracking, - ); - - // First we store the bounds in the parameters - self.updatable_structures - .reset_all_bounds_and_remove_fixed(context.as_readonly(), &self.parameters); + type PropagatorImpl = + TimeTableOverIntervalIncrementalPropagator; - self.is_time_table_outdated = true; + fn create(self, mut context: PropagatorConstructorContext) -> Self::PropagatorImpl { + let inference_code = context.create_inference_code(self.constraint_tag, TimeTable); - self.inference_code = Some(context.create_inference_code(self.constraint_tag, TimeTable)); - - self + TimeTableOverIntervalIncrementalPropagator::new( + context, + &self.tasks, + self.capacity, + self.cumulative_options, + inference_code, + ) } } @@ -147,24 +170,29 @@ impl< const SYNCHRONISE: bool, > TimeTableOverIntervalIncrementalPropagator { - pub(crate) fn new( + fn new( + mut context: PropagatorConstructorContext, arg_tasks: &[ArgTask], - capacity: i32, + capacity: CVar, cumulative_options: CumulativePropagatorOptions, - constraint_tag: ConstraintTag, + inference_code: InferenceCode, ) -> TimeTableOverIntervalIncrementalPropagator { - let tasks = create_tasks(arg_tasks); - let parameters = CumulativeParameters::new(tasks, capacity, cumulative_options); - let updatable_structures = UpdatableStructures::new(¶meters); + let tasks = create_tasks(context.as_readonly(), arg_tasks); + + let parameters = + CumulativeParameters::new(context.as_readonly(), tasks, capacity, cumulative_options); + register_tasks(¶meters.tasks, context.reborrow(), false); + + let mut updatable_structures = UpdatableStructures::new(¶meters); + updatable_structures.initialise_bounds_and_remove_fixed(context.as_readonly(), ¶meters); TimeTableOverIntervalIncrementalPropagator { time_table: Default::default(), parameters, updatable_structures, found_previous_conflict: false, - is_time_table_outdated: false, - constraint_tag, - inference_code: None, + is_time_table_outdated: true, + inference_code, } } @@ -185,18 +213,19 @@ impl< match determine_profiles_to_update(&self.time_table, &update_range) { Ok((start_index, end_index)) => { let result = insertion::insert_profiles_overlapping_with_added_mandatory_part( + context, &mut self.time_table, start_index, end_index, &update_range, task, - self.parameters.capacity, + self.parameters.capacity.clone(), ); if let Err(conflict_tasks) = result { if conflict.is_none() { conflict = Some(Err(create_conflict_explanation( context, - self.inference_code.unwrap(), + self.inference_code, &conflict_tasks, self.parameters.options.explanation_type, ) @@ -205,6 +234,7 @@ impl< } } Err(index_to_insert) => insertion::insert_profile_new_mandatory_part( + context, &mut self.time_table, index_to_insert, &update_range, @@ -222,6 +252,7 @@ impl< /// Removes the removed parts in the provided [`MandatoryPartAdjustments`] from the time-table fn remove_from_time_table( &mut self, + context: PropagationContext, mandatory_part_adjustments: &MandatoryPartAdjustments, task: &Rc>, ) { @@ -233,6 +264,7 @@ impl< match determine_profiles_to_update(&self.time_table, &update_range) { Ok((start_index, end_index)) => { removal::reduce_profiles_overlapping_with_added_mandatory_part( + context, &mut self.time_table, start_index, end_index, @@ -258,7 +290,7 @@ impl< self.time_table = create_time_table_over_interval_from_scratch( context.as_readonly(), &self.parameters, - self.inference_code.unwrap(), + self.inference_code, )?; // Then we note that the time-table is not outdated anymore @@ -279,13 +311,18 @@ impl< let element = self.updatable_structures.get_update_for_task(&updated_task); // We get the adjustments based on the stored updated - let mandatory_part_adjustments = element.get_mandatory_part_adjustments(); + let mandatory_part_adjustments = + element.get_mandatory_part_adjustments(context.as_readonly()); // Then we first remove from the time-table (if necessary) // // This order ensures that there is less of a chance of incorrect overflows being // reported - self.remove_from_time_table(&mandatory_part_adjustments, &updated_task); + self.remove_from_time_table( + context.as_readonly(), + &mandatory_part_adjustments, + &updated_task, + ); // Then we add to the time-table (if necessary) // @@ -311,15 +348,18 @@ impl< if SYNCHRONISE { // If we are synchronising then we need to search for the conflict which would have // been found by the non-incremental propagator - let conflicting_profile = - find_synchronised_conflict(&mut self.time_table, &self.parameters); + let conflicting_profile = find_synchronised_conflict( + context.as_readonly(), + &mut self.time_table, + &self.parameters, + ); // Now we need to find the same explanation as would have been found by // the non-incremental propagator if let Some(mut conflicting_profile) = conflicting_profile { let synchronised_conflict_explanation = create_synchronised_conflict_explanation( context.as_readonly(), - self.inference_code.unwrap(), + self.inference_code, &mut conflicting_profile, &self.parameters, ); @@ -328,7 +368,7 @@ impl< &synchronised_conflict_explanation, context.as_readonly(), &self.parameters, - self.inference_code.unwrap(), + self.inference_code, ), "The conflict explanation was not the same as the conflict explanation from scratch!" ); @@ -339,10 +379,9 @@ impl< self.found_previous_conflict = false; } else { // We linearly scan the profiles and find the first one which exceeds the capacity - let conflicting_profile = self - .time_table - .iter_mut() - .find(|profile| profile.height > self.parameters.capacity); + let conflicting_profile = self.time_table.iter_mut().find(|profile| { + profile.height > context.upper_bound(&self.parameters.capacity) + }); // If we have found such a conflict then we return it if let Some(conflicting_profile) = conflicting_profile { @@ -350,7 +389,7 @@ impl< create_time_table_over_interval_from_scratch( context.as_readonly(), &self.parameters, - self.inference_code.unwrap(), + self.inference_code, ) .is_err(), "Time-table from scratch could not find conflict" @@ -360,7 +399,7 @@ impl< return Err(create_conflict_explanation( context.as_readonly(), - self.inference_code.unwrap(), + self.inference_code, conflicting_profile, self.parameters.options.explanation_type, ) @@ -383,7 +422,7 @@ impl< pumpkin_assert_extreme!(self .time_table .iter() - .all(|profile| profile.height <= self.parameters.capacity)); + .all(|profile| profile.height <= context.upper_bound(&self.parameters.capacity))); Ok(()) } } @@ -410,16 +449,16 @@ impl< if self.parameters.is_infeasible { return Err(Inconsistency::Conflict(PropagatorConflict { conjunction: conjunction!(), - inference_code: self.inference_code.unwrap(), + inference_code: self.inference_code, })); } self.update_time_table(&mut context)?; pumpkin_assert_extreme!( - debug::time_tables_are_the_same_interval::( + debug::time_tables_are_the_same_interval::( context.as_readonly(), - self.inference_code.unwrap(), + self.inference_code, &self.time_table, &self.parameters, ), @@ -432,7 +471,7 @@ impl< // could cause another propagation by a profile which has not been updated propagate_based_on_timetable( &mut context, - self.inference_code.unwrap(), + self.inference_code, self.time_table.iter(), &self.parameters, &mut self.updatable_structures, @@ -542,7 +581,7 @@ impl< &mut context, &self.parameters, &self.updatable_structures, - self.inference_code.unwrap(), + self.inference_code, ) } } @@ -560,7 +599,6 @@ fn determine_profiles_to_update< Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, - CVar: IntegerVariable + 'static, >( time_table: &OverIntervalTimeTableType, update_range: &Range, @@ -634,7 +672,6 @@ fn find_overlapping_profile< Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, - CVar: IntegerVariable + 'static, >( time_table: &OverIntervalTimeTableType, update_range: &Range, @@ -658,14 +695,14 @@ fn find_overlapping_profile< mod tests { use crate::basic_types::Inconsistency; use crate::conjunction; + use crate::constraint_arguments::CumulativeExplanationType; use crate::engine::predicates::predicate::Predicate; use crate::engine::propagation::EnqueueDecision; use crate::engine::test_solver::TestSolver; - use crate::options::CumulativeExplanationType; use crate::predicate; use crate::propagators::ArgTask; use crate::propagators::CumulativePropagatorOptions; - use crate::propagators::TimeTableOverIntervalIncrementalPropagator; + use crate::propagators::TimeTableOverIntervalIncrementalConstructor; use crate::variables::DomainId; #[test] @@ -676,27 +713,31 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let _ = solver - .new_propagator( - TimeTableOverIntervalIncrementalPropagator::::new( - &[ - ArgTask { - start_time: s1, - processing_time: 4, - resource_usage: 1, - }, - ArgTask { - start_time: s2, - processing_time: 3, - resource_usage: 1, - }, - ] - .into_iter() - .collect::>(), - 1, - CumulativePropagatorOptions::default(), - constraint_tag, - ), - ) + .new_propagator(TimeTableOverIntervalIncrementalConstructor::< + DomainId, + i32, + i32, + i32, + false, + >::new( + [ + ArgTask { + start_time: s1, + processing_time: 4, + resource_usage: 1, + }, + ArgTask { + start_time: s2, + processing_time: 3, + resource_usage: 1, + }, + ] + .into_iter() + .collect::>(), + 1, + CumulativePropagatorOptions::default(), + constraint_tag, + )) .expect("No conflict"); assert_eq!(solver.lower_bound(s2), 5); assert_eq!(solver.upper_bound(s2), 8); @@ -711,11 +752,14 @@ mod tests { let s2 = solver.new_variable(1, 1); let constraint_tag = solver.new_constraint_tag(); - let result = solver.new_propagator(TimeTableOverIntervalIncrementalPropagator::< + let result = solver.new_propagator(TimeTableOverIntervalIncrementalConstructor::< DomainId, + i32, + i32, + i32, false, >::new( - &[ + [ ArgTask { start_time: s1, processing_time: 4, @@ -766,27 +810,31 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let _ = solver - .new_propagator( - TimeTableOverIntervalIncrementalPropagator::::new( - &[ - ArgTask { - start_time: s1, - processing_time: 4, - resource_usage: 1, - }, - ArgTask { - start_time: s2, - processing_time: 3, - resource_usage: 1, - }, - ] - .into_iter() - .collect::>(), - 1, - CumulativePropagatorOptions::default(), - constraint_tag, - ), - ) + .new_propagator(TimeTableOverIntervalIncrementalConstructor::< + DomainId, + i32, + i32, + i32, + false, + >::new( + [ + ArgTask { + start_time: s1, + processing_time: 4, + resource_usage: 1, + }, + ArgTask { + start_time: s2, + processing_time: 3, + resource_usage: 1, + }, + ] + .into_iter() + .collect::>(), + 1, + CumulativePropagatorOptions::default(), + constraint_tag, + )) .expect("No conflict"); assert_eq!(solver.lower_bound(s2), 0); assert_eq!(solver.upper_bound(s2), 6); @@ -806,47 +854,51 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let _ = solver - .new_propagator( - TimeTableOverIntervalIncrementalPropagator::::new( - &[ - ArgTask { - start_time: a, - processing_time: 2, - resource_usage: 1, - }, - ArgTask { - start_time: b, - processing_time: 6, - resource_usage: 2, - }, - ArgTask { - start_time: c, - processing_time: 2, - resource_usage: 4, - }, - ArgTask { - start_time: d, - processing_time: 2, - resource_usage: 2, - }, - ArgTask { - start_time: e, - processing_time: 5, - resource_usage: 2, - }, - ArgTask { - start_time: f, - processing_time: 6, - resource_usage: 2, - }, - ] - .into_iter() - .collect::>(), - 5, - CumulativePropagatorOptions::default(), - constraint_tag, - ), - ) + .new_propagator(TimeTableOverIntervalIncrementalConstructor::< + DomainId, + i32, + i32, + i32, + false, + >::new( + [ + ArgTask { + start_time: a, + processing_time: 2, + resource_usage: 1, + }, + ArgTask { + start_time: b, + processing_time: 6, + resource_usage: 2, + }, + ArgTask { + start_time: c, + processing_time: 2, + resource_usage: 4, + }, + ArgTask { + start_time: d, + processing_time: 2, + resource_usage: 2, + }, + ArgTask { + start_time: e, + processing_time: 5, + resource_usage: 2, + }, + ArgTask { + start_time: f, + processing_time: 6, + resource_usage: 2, + }, + ] + .into_iter() + .collect::>(), + 5, + CumulativePropagatorOptions::default(), + constraint_tag, + )) .expect("No conflict"); assert_eq!(solver.lower_bound(f), 10); } @@ -859,27 +911,31 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let propagator = solver - .new_propagator( - TimeTableOverIntervalIncrementalPropagator::::new( - &[ - ArgTask { - start_time: s1, - processing_time: 2, - resource_usage: 1, - }, - ArgTask { - start_time: s2, - processing_time: 3, - resource_usage: 1, - }, - ] - .into_iter() - .collect::>(), - 1, - CumulativePropagatorOptions::default(), - constraint_tag, - ), - ) + .new_propagator(TimeTableOverIntervalIncrementalConstructor::< + DomainId, + i32, + i32, + i32, + false, + >::new( + [ + ArgTask { + start_time: s1, + processing_time: 2, + resource_usage: 1, + }, + ArgTask { + start_time: s2, + processing_time: 3, + resource_usage: 1, + }, + ] + .into_iter() + .collect::>(), + 1, + CumulativePropagatorOptions::default(), + constraint_tag, + )) .expect("No conflict"); assert_eq!(solver.lower_bound(s2), 6); assert_eq!(solver.upper_bound(s2), 10); @@ -907,30 +963,34 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let _ = solver - .new_propagator( - TimeTableOverIntervalIncrementalPropagator::::new( - &[ - ArgTask { - start_time: s1, - processing_time: 4, - resource_usage: 1, - }, - ArgTask { - start_time: s2, - processing_time: 3, - resource_usage: 1, - }, - ] - .into_iter() - .collect::>(), - 1, - CumulativePropagatorOptions { - explanation_type: CumulativeExplanationType::Naive, - ..Default::default() + .new_propagator(TimeTableOverIntervalIncrementalConstructor::< + DomainId, + i32, + i32, + i32, + false, + >::new( + [ + ArgTask { + start_time: s1, + processing_time: 4, + resource_usage: 1, }, - constraint_tag, - ), - ) + ArgTask { + start_time: s2, + processing_time: 3, + resource_usage: 1, + }, + ] + .into_iter() + .collect::>(), + 1, + CumulativePropagatorOptions { + explanation_type: CumulativeExplanationType::Naive, + ..Default::default() + }, + constraint_tag, + )) .expect("No conflict"); assert_eq!(solver.lower_bound(s2), 1); assert_eq!(solver.upper_bound(s2), 3); @@ -953,47 +1013,51 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let propagator = solver - .new_propagator( - TimeTableOverIntervalIncrementalPropagator::::new( - &[ - ArgTask { - start_time: a, - processing_time: 2, - resource_usage: 1, - }, - ArgTask { - start_time: b, - processing_time: 6, - resource_usage: 2, - }, - ArgTask { - start_time: c, - processing_time: 2, - resource_usage: 4, - }, - ArgTask { - start_time: d, - processing_time: 2, - resource_usage: 2, - }, - ArgTask { - start_time: e, - processing_time: 4, - resource_usage: 2, - }, - ArgTask { - start_time: f, - processing_time: 6, - resource_usage: 2, - }, - ] - .into_iter() - .collect::>(), - 5, - CumulativePropagatorOptions::default(), - constraint_tag, - ), - ) + .new_propagator(TimeTableOverIntervalIncrementalConstructor::< + DomainId, + i32, + i32, + i32, + false, + >::new( + [ + ArgTask { + start_time: a, + processing_time: 2, + resource_usage: 1, + }, + ArgTask { + start_time: b, + processing_time: 6, + resource_usage: 2, + }, + ArgTask { + start_time: c, + processing_time: 2, + resource_usage: 4, + }, + ArgTask { + start_time: d, + processing_time: 2, + resource_usage: 2, + }, + ArgTask { + start_time: e, + processing_time: 4, + resource_usage: 2, + }, + ArgTask { + start_time: f, + processing_time: 6, + resource_usage: 2, + }, + ] + .into_iter() + .collect::>(), + 5, + CumulativePropagatorOptions::default(), + constraint_tag, + )) .expect("No conflict"); assert_eq!(solver.lower_bound(a), 0); assert_eq!(solver.upper_bound(a), 1); @@ -1031,52 +1095,56 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let propagator = solver - .new_propagator( - TimeTableOverIntervalIncrementalPropagator::::new( - &[ - ArgTask { - start_time: a, - processing_time: 2, - resource_usage: 1, - }, - ArgTask { - start_time: b1, - processing_time: 2, - resource_usage: 2, - }, - ArgTask { - start_time: b2, - processing_time: 3, - resource_usage: 2, - }, - ArgTask { - start_time: c, - processing_time: 2, - resource_usage: 4, - }, - ArgTask { - start_time: d, - processing_time: 2, - resource_usage: 2, - }, - ArgTask { - start_time: e, - processing_time: 4, - resource_usage: 2, - }, - ArgTask { - start_time: f, - processing_time: 6, - resource_usage: 2, - }, - ] - .into_iter() - .collect::>(), - 5, - CumulativePropagatorOptions::default(), - constraint_tag, - ), - ) + .new_propagator(TimeTableOverIntervalIncrementalConstructor::< + DomainId, + i32, + i32, + i32, + false, + >::new( + [ + ArgTask { + start_time: a, + processing_time: 2, + resource_usage: 1, + }, + ArgTask { + start_time: b1, + processing_time: 2, + resource_usage: 2, + }, + ArgTask { + start_time: b2, + processing_time: 3, + resource_usage: 2, + }, + ArgTask { + start_time: c, + processing_time: 2, + resource_usage: 4, + }, + ArgTask { + start_time: d, + processing_time: 2, + resource_usage: 2, + }, + ArgTask { + start_time: e, + processing_time: 4, + resource_usage: 2, + }, + ArgTask { + start_time: f, + processing_time: 6, + resource_usage: 2, + }, + ] + .into_iter() + .collect::>(), + 5, + CumulativePropagatorOptions::default(), + constraint_tag, + )) .expect("No conflict"); assert_eq!(solver.lower_bound(a), 0); assert_eq!(solver.upper_bound(a), 1); @@ -1107,30 +1175,34 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let _ = solver - .new_propagator( - TimeTableOverIntervalIncrementalPropagator::::new( - &[ - ArgTask { - start_time: s1, - processing_time: 4, - resource_usage: 1, - }, - ArgTask { - start_time: s2, - processing_time: 3, - resource_usage: 1, - }, - ] - .into_iter() - .collect::>(), - 1, - CumulativePropagatorOptions { - explanation_type: CumulativeExplanationType::Naive, - ..Default::default() + .new_propagator(TimeTableOverIntervalIncrementalConstructor::< + DomainId, + i32, + i32, + i32, + false, + >::new( + [ + ArgTask { + start_time: s1, + processing_time: 4, + resource_usage: 1, }, - constraint_tag, - ), - ) + ArgTask { + start_time: s2, + processing_time: 3, + resource_usage: 1, + }, + ] + .into_iter() + .collect::>(), + 1, + CumulativePropagatorOptions { + explanation_type: CumulativeExplanationType::Naive, + ..Default::default() + }, + constraint_tag, + )) .expect("No conflict"); assert_eq!(solver.lower_bound(s2), 5); assert_eq!(solver.upper_bound(s2), 8); @@ -1150,35 +1222,39 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let _ = solver - .new_propagator( - TimeTableOverIntervalIncrementalPropagator::::new( - &[ - ArgTask { - start_time: s1, - processing_time: 2, - resource_usage: 1, - }, - ArgTask { - start_time: s2, - processing_time: 2, - resource_usage: 1, - }, - ArgTask { - start_time: s3, - processing_time: 4, - resource_usage: 1, - }, - ] - .into_iter() - .collect::>(), - 1, - CumulativePropagatorOptions { - explanation_type: CumulativeExplanationType::Naive, - ..Default::default() + .new_propagator(TimeTableOverIntervalIncrementalConstructor::< + DomainId, + i32, + i32, + i32, + false, + >::new( + [ + ArgTask { + start_time: s1, + processing_time: 2, + resource_usage: 1, }, - constraint_tag, - ), - ) + ArgTask { + start_time: s2, + processing_time: 2, + resource_usage: 1, + }, + ArgTask { + start_time: s3, + processing_time: 4, + resource_usage: 1, + }, + ] + .into_iter() + .collect::>(), + 1, + CumulativePropagatorOptions { + explanation_type: CumulativeExplanationType::Naive, + ..Default::default() + }, + constraint_tag, + )) .expect("No conflict"); assert_eq!(solver.lower_bound(s3), 7); assert_eq!(solver.upper_bound(s3), 15); @@ -1199,31 +1275,35 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let _ = solver - .new_propagator( - TimeTableOverIntervalIncrementalPropagator::::new( - &[ - ArgTask { - start_time: s1, - processing_time: 4, - resource_usage: 1, - }, - ArgTask { - start_time: s2, - processing_time: 3, - resource_usage: 1, - }, - ] - .into_iter() - .collect::>(), - 1, - CumulativePropagatorOptions { - explanation_type: CumulativeExplanationType::Naive, - allow_holes_in_domain: true, - ..Default::default() + .new_propagator(TimeTableOverIntervalIncrementalConstructor::< + DomainId, + i32, + i32, + i32, + false, + >::new( + [ + ArgTask { + start_time: s1, + processing_time: 4, + resource_usage: 1, }, - constraint_tag, - ), - ) + ArgTask { + start_time: s2, + processing_time: 3, + resource_usage: 1, + }, + ] + .into_iter() + .collect::>(), + 1, + CumulativePropagatorOptions { + explanation_type: CumulativeExplanationType::Naive, + allow_holes_in_domain: true, + ..Default::default() + }, + constraint_tag, + )) .expect("No conflict"); assert_eq!(solver.lower_bound(s2), 0); assert_eq!(solver.upper_bound(s2), 8); diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/synchronisation.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/synchronisation.rs index 88732ccc2..5338ce915 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/synchronisation.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/synchronisation.rs @@ -52,6 +52,7 @@ pub(crate) fn find_synchronised_conflict< RVar: IntegerVariable + 'static, CVar: IntegerVariable + 'static, >( + context: PropagationContext, time_table: &mut PerPointTimeTableType, parameters: &CumulativeParameters, ) -> Option { @@ -60,7 +61,7 @@ pub(crate) fn find_synchronised_conflict< // We go over every profile for (time_point, profile) in time_table.iter_mut() { - if profile.height <= parameters.capacity { + if profile.height <= context.upper_bound(¶meters.capacity) { // If the profile cannot overflow the resource capacity then we move onto the next // profile continue; @@ -70,10 +71,14 @@ pub(crate) fn find_synchronised_conflict< // is overflown and we get the last element in this set (which has the one with the maximum // ID since the profile is sorted in the method based on ID) let mut new_height = 0; - let conflicting_tasks = - get_minimum_set_of_tasks_which_overflow_capacity(profile, parameters, &mut new_height); + let conflicting_tasks = get_minimum_set_of_tasks_which_overflow_capacity( + context, + profile, + parameters, + &mut new_height, + ); if let Some(task_with_maximum_id) = conflicting_tasks.last() { - pumpkin_assert_moderate!(new_height > parameters.capacity); + pumpkin_assert_moderate!(new_height > context.upper_bound(¶meters.capacity)); if task_with_maximum_id.id.unpack() < minimum_maximum_id { minimum_maximum_id = task_with_maximum_id.id.unpack(); profile_time_point = Some(*time_point); @@ -145,6 +150,7 @@ pub(crate) fn create_synchronised_conflict_explanation< // would have been found by the non-incremental propagator; // we thus sort on the IDs and take the first `n` tasks which lead to an overflow let new_profile = get_minimum_set_of_tasks_which_overflow_capacity( + context, conflicting_profile, parameters, &mut new_height, @@ -198,6 +204,7 @@ mod tests { use super::find_synchronised_conflict; use crate::engine::propagation::LocalId; + use crate::engine::propagation::PropagationContext; use crate::engine::test_solver::TestSolver; use crate::propagators::CumulativeParameters; use crate::propagators::CumulativePropagatorOptions; @@ -234,8 +241,14 @@ mod tests { }, ]; - let parameters = - CumulativeParameters::new(tasks, 1, CumulativePropagatorOptions::default()); + let parameters = CumulativeParameters::new( + PropagationContext { + assignments: &solver.assignments, + }, + tasks, + 1, + CumulativePropagatorOptions::default(), + ); let mut time_table = PerPointTimeTableType::default(); let _ = time_table.insert( @@ -260,7 +273,13 @@ mod tests { }, ); - let result = find_synchronised_conflict(&mut time_table, ¶meters); + let result = find_synchronised_conflict( + PropagationContext { + assignments: &solver.assignments, + }, + &mut time_table, + ¶meters, + ); assert!(matches!(result, Some(4))); } } diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs index 9618ee4fa..9925529c1 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs @@ -1,3 +1,4 @@ +use crate::engine::cp::propagation::contexts::propagation_context::ReadDomains; use std::collections::btree_map::Entry; use std::collections::BTreeMap; use std::fmt::Debug; @@ -98,10 +99,38 @@ pub(crate) struct TimeTablePerPointIncrementalPropagator< /// [`CumulativePropagatorOptions::incremental_backtracking`] is set to false. is_time_table_outdated: bool, - // TODO: This should be refactored to use a separate propagator constructor, but that is a lot - // of work in this module and I don't know enough about it to not break it. + inference_code: InferenceCode, +} + +pub(crate) struct TimeTablePerPointIncrementalConstructor< + Var, + PVar, + RVar, + CVar, + const SYNCHRONISE: bool, +> { + tasks: Vec>, + capacity: CVar, + cumulative_options: CumulativePropagatorOptions, constraint_tag: ConstraintTag, - inference_code: Option, +} + +impl + TimeTablePerPointIncrementalConstructor +{ + pub(crate) fn new( + arg_tasks: Vec>, + capacity: CVar, + cumulative_options: CumulativePropagatorOptions, + constraint_tag: ConstraintTag, + ) -> Self { + Self { + tasks: arg_tasks, + capacity, + cumulative_options, + constraint_tag, + } + } } impl< @@ -111,21 +140,21 @@ impl< CVar: IntegerVariable + 'static, const SYNCHRONISE: bool, > PropagatorConstructor - for TimeTablePerPointIncrementalPropagator + for TimeTablePerPointIncrementalConstructor { - type PropagatorImpl = Self; + type PropagatorImpl = + TimeTablePerPointIncrementalPropagator; - fn create(mut self, mut context: PropagatorConstructorContext) -> Self::PropagatorImpl { - register_tasks(&self.parameters.tasks, context.reborrow(), true); - self.updatable_structures - .reset_all_bounds_and_remove_fixed(context.as_readonly(), &self.parameters); + fn create(self, mut context: PropagatorConstructorContext) -> Self::PropagatorImpl { + let inference_code = context.create_inference_code(self.constraint_tag, TimeTable); - // Then we do normal propagation - self.is_time_table_outdated = true; - - self.inference_code = Some(context.create_inference_code(self.constraint_tag, TimeTable)); - - self + TimeTablePerPointIncrementalPropagator::new( + context, + &self.tasks, + self.capacity, + self.cumulative_options, + inference_code, + ) } } @@ -137,23 +166,29 @@ impl< const SYNCHRONISE: bool, > TimeTablePerPointIncrementalPropagator { - pub(crate) fn new( + fn new( + mut context: PropagatorConstructorContext, arg_tasks: &[ArgTask], capacity: CVar, cumulative_options: CumulativePropagatorOptions, - constraint_tag: ConstraintTag, + inference_code: InferenceCode, ) -> TimeTablePerPointIncrementalPropagator { - let tasks = create_tasks(arg_tasks); - let parameters = CumulativeParameters::new(tasks, capacity, cumulative_options); - let updatable_structures = UpdatableStructures::new(¶meters); + let tasks = create_tasks(context.as_readonly(), arg_tasks); + + let parameters = + CumulativeParameters::new(context.as_readonly(), tasks, capacity, cumulative_options); + register_tasks(¶meters.tasks, context.reborrow(), false); + + let mut updatable_structures = UpdatableStructures::new(¶meters); + updatable_structures.initialise_bounds_and_remove_fixed(context.as_readonly(), ¶meters); + TimeTablePerPointIncrementalPropagator { time_table: BTreeMap::new(), parameters, updatable_structures, found_previous_conflict: false, - is_time_table_outdated: false, - constraint_tag, - inference_code: None, + is_time_table_outdated: true, + inference_code, } } @@ -182,14 +217,16 @@ impl< .entry(time_point as u32) .or_insert(ResourceProfile::default(time_point)); - current_profile.height += task.resource_usage; + current_profile.height += context.lower_bound(&task.resource_usage); current_profile.profile_tasks.push(Rc::clone(task)); - if current_profile.height > self.parameters.capacity && conflict.is_none() { + if current_profile.height > context.upper_bound(&self.parameters.capacity) + && conflict.is_none() + { // The newly introduced mandatory part(s) caused an overflow of the resource conflict = Some(Err(create_conflict_explanation( context, - self.inference_code.unwrap(), + self.inference_code, current_profile, self.parameters.options.explanation_type, ) @@ -208,6 +245,7 @@ impl< /// Removes the removed parts in the provided [`MandatoryPartAdjustments`] from the time-table fn remove_from_time_table( &mut self, + context: PropagationContext, mandatory_part_adjustments: &MandatoryPartAdjustments, task: &Rc>, ) { @@ -222,7 +260,7 @@ impl< .entry(time_point as u32) .and_modify(|profile| { // We remove the resource usage of the task from the height of the profile - profile.height -= task.resource_usage; + profile.height -= context.lower_bound(&task.resource_usage); // If the height of the profile is not equal to 0 then we remove the task // from the profile tasks @@ -256,7 +294,7 @@ impl< // We create the time-table from scratch (and return an error if it overflows) self.time_table = create_time_table_per_point_from_scratch( context.as_readonly(), - self.inference_code.unwrap(), + self.inference_code, &self.parameters, )?; @@ -278,13 +316,18 @@ impl< let element = self.updatable_structures.get_update_for_task(&updated_task); // We get the adjustments based on the stored updated - let mandatory_part_adjustments = element.get_mandatory_part_adjustments(); + let mandatory_part_adjustments = + element.get_mandatory_part_adjustments(context.as_readonly()); // Then we first remove from the time-table (if necessary) // // This order ensures that there is less of a chance of incorrect overflows being // reported - self.remove_from_time_table(&mandatory_part_adjustments, &updated_task); + self.remove_from_time_table( + context.as_readonly(), + &mandatory_part_adjustments, + &updated_task, + ); // Then we add to the time-table (if necessary) // @@ -310,8 +353,11 @@ impl< if SYNCHRONISE { // If we are synchronising then we need to search for the conflict which would have // been found by the non-incremental propagator - let synchronised_conflict = - find_synchronised_conflict(&mut self.time_table, &self.parameters); + let synchronised_conflict = find_synchronised_conflict( + context.as_readonly(), + &mut self.time_table, + &self.parameters, + ); // After finding the profile which would have been found by the non-incremental // propagator, we also need to find the profile explanation which would have been @@ -324,7 +370,7 @@ impl< let synchronised_conflict_explanation = create_synchronised_conflict_explanation( context.as_readonly(), - self.inference_code.unwrap(), + self.inference_code, conflicting_profile, &self.parameters, ); @@ -333,7 +379,7 @@ impl< check_synchronisation_conflict_explanation_per_point( &synchronised_conflict_explanation, context.as_readonly(), - self.inference_code.unwrap(), + self.inference_code, &self.parameters, ), "The conflict explanation was not the same as the conflict explanation from scratch!" @@ -349,17 +395,16 @@ impl< self.found_previous_conflict = false; } else { // We linearly scan the profiles and find the first one which exceeds the capacity - let conflicting_profile = self - .time_table - .values_mut() - .find(|profile| profile.height > self.parameters.capacity); + let conflicting_profile = self.time_table.values_mut().find(|profile| { + profile.height > context.upper_bound(&self.parameters.capacity) + }); // If we have found such a conflict then we return it if let Some(conflicting_profile) = conflicting_profile { pumpkin_assert_extreme!( create_time_table_per_point_from_scratch( context.as_readonly(), - self.inference_code.unwrap(), + self.inference_code, &self.parameters ) .is_err(), @@ -370,7 +415,7 @@ impl< return Err(create_conflict_explanation( context.as_readonly(), - self.inference_code.unwrap(), + self.inference_code, conflicting_profile, self.parameters.options.explanation_type, ) @@ -393,7 +438,7 @@ impl< pumpkin_assert_extreme!(self .time_table .values() - .all(|profile| profile.height <= self.parameters.capacity)); + .all(|profile| profile.height <= context.upper_bound(&self.parameters.capacity))); Ok(()) } } @@ -419,16 +464,22 @@ impl< if self.parameters.is_infeasible { return Err(Inconsistency::Conflict(PropagatorConflict { conjunction: conjunction!(), - inference_code: self.inference_code.unwrap(), + inference_code: self.inference_code, })); } // We update the time-table based on the stored updates self.update_time_table(&mut context)?; - pumpkin_assert_extreme!(debug::time_tables_are_the_same_point::( + pumpkin_assert_extreme!(debug::time_tables_are_the_same_point::< + Var, + PVar, + RVar, + CVar, + SYNCHRONISE, + >( context.as_readonly(), - self.inference_code.unwrap(), + self.inference_code, &self.time_table, &self.parameters )); @@ -439,7 +490,7 @@ impl< // could cause another propagation by a profile which has not been updated propagate_based_on_timetable( &mut context, - self.inference_code.unwrap(), + self.inference_code, self.time_table.values(), &self.parameters, &mut self.updatable_structures, @@ -545,7 +596,7 @@ impl< // Use the same debug propagator from `TimeTablePerPoint` debug_propagate_from_scratch_time_table_point( &mut context, - self.inference_code.unwrap(), + self.inference_code, &self.parameters, &self.updatable_structures, ) @@ -628,16 +679,16 @@ mod debug { mod tests { use crate::basic_types::Inconsistency; use crate::conjunction; + use crate::constraint_arguments::CumulativeExplanationType; use crate::engine::predicates::predicate::Predicate; use crate::engine::propagation::EnqueueDecision; use crate::engine::test_solver::TestSolver; - use crate::options::CumulativeExplanationType; use crate::predicate; use crate::predicates::PredicateConstructor; use crate::propagators::ArgTask; use crate::propagators::CumulativePropagatorOptions; - use crate::propagators::TimeTablePerPointIncrementalPropagator; - use crate::propagators::TimeTablePerPointPropagator; + use crate::propagators::TimeTablePerPointConstructor; + use crate::propagators::TimeTablePerPointIncrementalConstructor; use crate::variables::DomainId; #[test] @@ -648,27 +699,31 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let _ = solver - .new_propagator( - TimeTablePerPointIncrementalPropagator::::new( - &[ - ArgTask { - start_time: s1, - processing_time: 4, - resource_usage: 1, - }, - ArgTask { - start_time: s2, - processing_time: 3, - resource_usage: 1, - }, - ] - .into_iter() - .collect::>(), - 1, - CumulativePropagatorOptions::default(), - constraint_tag, - ), - ) + .new_propagator(TimeTablePerPointIncrementalConstructor::< + DomainId, + i32, + i32, + i32, + false, + >::new( + [ + ArgTask { + start_time: s1, + processing_time: 4, + resource_usage: 1, + }, + ArgTask { + start_time: s2, + processing_time: 3, + resource_usage: 1, + }, + ] + .into_iter() + .collect::>(), + 1, + CumulativePropagatorOptions::default(), + constraint_tag, + )) .expect("No conflict"); assert_eq!(solver.lower_bound(s2), 5); assert_eq!(solver.upper_bound(s2), 8); @@ -683,30 +738,34 @@ mod tests { let s2 = solver.new_variable(1, 1); let constraint_tag = solver.new_constraint_tag(); - let result = solver.new_propagator( - TimeTablePerPointIncrementalPropagator::::new( - &[ - ArgTask { - start_time: s1, - processing_time: 4, - resource_usage: 1, - }, - ArgTask { - start_time: s2, - processing_time: 4, - resource_usage: 1, - }, - ] - .into_iter() - .collect::>(), - 1, - CumulativePropagatorOptions { - explanation_type: CumulativeExplanationType::Naive, - ..Default::default() + let result = solver.new_propagator(TimeTablePerPointIncrementalConstructor::< + DomainId, + i32, + i32, + i32, + false, + >::new( + [ + ArgTask { + start_time: s1, + processing_time: 4, + resource_usage: 1, }, - constraint_tag, - ), - ); + ArgTask { + start_time: s2, + processing_time: 4, + resource_usage: 1, + }, + ] + .into_iter() + .collect::>(), + 1, + CumulativePropagatorOptions { + explanation_type: CumulativeExplanationType::Naive, + ..Default::default() + }, + constraint_tag, + )); assert!(match result { Err(Inconsistency::Conflict(x)) => { let expected = [ @@ -734,27 +793,31 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let _ = solver - .new_propagator( - TimeTablePerPointIncrementalPropagator::::new( - &[ - ArgTask { - start_time: s1, - processing_time: 4, - resource_usage: 1, - }, - ArgTask { - start_time: s2, - processing_time: 3, - resource_usage: 1, - }, - ] - .into_iter() - .collect::>(), - 1, - CumulativePropagatorOptions::default(), - constraint_tag, - ), - ) + .new_propagator(TimeTablePerPointIncrementalConstructor::< + DomainId, + i32, + i32, + i32, + false, + >::new( + [ + ArgTask { + start_time: s1, + processing_time: 4, + resource_usage: 1, + }, + ArgTask { + start_time: s2, + processing_time: 3, + resource_usage: 1, + }, + ] + .into_iter() + .collect::>(), + 1, + CumulativePropagatorOptions::default(), + constraint_tag, + )) .expect("No conflict"); assert_eq!(solver.lower_bound(s2), 0); assert_eq!(solver.upper_bound(s2), 6); @@ -774,47 +837,51 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let _ = solver - .new_propagator( - TimeTablePerPointIncrementalPropagator::::new( - &[ - ArgTask { - start_time: a, - processing_time: 2, - resource_usage: 1, - }, - ArgTask { - start_time: b, - processing_time: 6, - resource_usage: 2, - }, - ArgTask { - start_time: c, - processing_time: 2, - resource_usage: 4, - }, - ArgTask { - start_time: d, - processing_time: 2, - resource_usage: 2, - }, - ArgTask { - start_time: e, - processing_time: 5, - resource_usage: 2, - }, - ArgTask { - start_time: f, - processing_time: 6, - resource_usage: 2, - }, - ] - .into_iter() - .collect::>(), - 5, - CumulativePropagatorOptions::default(), - constraint_tag, - ), - ) + .new_propagator(TimeTablePerPointIncrementalConstructor::< + DomainId, + i32, + i32, + i32, + false, + >::new( + [ + ArgTask { + start_time: a, + processing_time: 2, + resource_usage: 1, + }, + ArgTask { + start_time: b, + processing_time: 6, + resource_usage: 2, + }, + ArgTask { + start_time: c, + processing_time: 2, + resource_usage: 4, + }, + ArgTask { + start_time: d, + processing_time: 2, + resource_usage: 2, + }, + ArgTask { + start_time: e, + processing_time: 5, + resource_usage: 2, + }, + ArgTask { + start_time: f, + processing_time: 6, + resource_usage: 2, + }, + ] + .into_iter() + .collect::>(), + 5, + CumulativePropagatorOptions::default(), + constraint_tag, + )) .expect("No conflict"); assert_eq!(solver.lower_bound(f), 10); } @@ -827,27 +894,31 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let propagator = solver - .new_propagator( - TimeTablePerPointIncrementalPropagator::::new( - &[ - ArgTask { - start_time: s1, - processing_time: 2, - resource_usage: 1, - }, - ArgTask { - start_time: s2, - processing_time: 3, - resource_usage: 1, - }, - ] - .into_iter() - .collect::>(), - 1, - CumulativePropagatorOptions::default(), - constraint_tag, - ), - ) + .new_propagator(TimeTablePerPointIncrementalConstructor::< + DomainId, + i32, + i32, + i32, + false, + >::new( + [ + ArgTask { + start_time: s1, + processing_time: 2, + resource_usage: 1, + }, + ArgTask { + start_time: s2, + processing_time: 3, + resource_usage: 1, + }, + ] + .into_iter() + .collect::>(), + 1, + CumulativePropagatorOptions::default(), + constraint_tag, + )) .expect("No conflict"); assert_eq!(solver.lower_bound(s2), 6); assert_eq!(solver.upper_bound(s2), 10); @@ -875,30 +946,34 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let propagator = solver - .new_propagator( - TimeTablePerPointIncrementalPropagator::::new( - &[ - ArgTask { - start_time: s1, - processing_time: 4, - resource_usage: 1, - }, - ArgTask { - start_time: s2, - processing_time: 3, - resource_usage: 1, - }, - ] - .into_iter() - .collect::>(), - 1, - CumulativePropagatorOptions { - explanation_type: CumulativeExplanationType::Naive, - ..Default::default() - }, - constraint_tag, - ), - ) + .new_propagator(TimeTablePerPointIncrementalConstructor::< + DomainId, + i32, + i32, + i32, + false, + >::new( + [ + ArgTask { + start_time: s1, + processing_time: 4, + resource_usage: 1, + }, + ArgTask { + start_time: s2, + processing_time: 3, + resource_usage: 1, + }, + ] + .into_iter() + .collect::>(), + 1, + CumulativePropagatorOptions { + explanation_type: CumulativeExplanationType::Naive, + ..Default::default() + }, + constraint_tag, + )) .expect("No conflict"); let result = solver.propagate_until_fixed_point(propagator); assert!(result.is_ok()); @@ -923,47 +998,51 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let propagator = solver - .new_propagator( - TimeTablePerPointIncrementalPropagator::::new( - &[ - ArgTask { - start_time: a, - processing_time: 2, - resource_usage: 1, - }, - ArgTask { - start_time: b, - processing_time: 6, - resource_usage: 2, - }, - ArgTask { - start_time: c, - processing_time: 2, - resource_usage: 4, - }, - ArgTask { - start_time: d, - processing_time: 2, - resource_usage: 2, - }, - ArgTask { - start_time: e, - processing_time: 4, - resource_usage: 2, - }, - ArgTask { - start_time: f, - processing_time: 6, - resource_usage: 2, - }, - ] - .into_iter() - .collect::>(), - 5, - CumulativePropagatorOptions::default(), - constraint_tag, - ), - ) + .new_propagator(TimeTablePerPointIncrementalConstructor::< + DomainId, + i32, + i32, + i32, + false, + >::new( + [ + ArgTask { + start_time: a, + processing_time: 2, + resource_usage: 1, + }, + ArgTask { + start_time: b, + processing_time: 6, + resource_usage: 2, + }, + ArgTask { + start_time: c, + processing_time: 2, + resource_usage: 4, + }, + ArgTask { + start_time: d, + processing_time: 2, + resource_usage: 2, + }, + ArgTask { + start_time: e, + processing_time: 4, + resource_usage: 2, + }, + ArgTask { + start_time: f, + processing_time: 6, + resource_usage: 2, + }, + ] + .into_iter() + .collect::>(), + 5, + CumulativePropagatorOptions::default(), + constraint_tag, + )) .expect("No conflict"); assert_eq!(solver.lower_bound(a), 0); assert_eq!(solver.upper_bound(a), 1); @@ -1001,52 +1080,56 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let propagator = solver - .new_propagator( - TimeTablePerPointIncrementalPropagator::::new( - &[ - ArgTask { - start_time: a, - processing_time: 2, - resource_usage: 1, - }, - ArgTask { - start_time: b1, - processing_time: 2, - resource_usage: 2, - }, - ArgTask { - start_time: b2, - processing_time: 3, - resource_usage: 2, - }, - ArgTask { - start_time: c, - processing_time: 2, - resource_usage: 4, - }, - ArgTask { - start_time: d, - processing_time: 2, - resource_usage: 2, - }, - ArgTask { - start_time: e, - processing_time: 4, - resource_usage: 2, - }, - ArgTask { - start_time: f, - processing_time: 6, - resource_usage: 2, - }, - ] - .into_iter() - .collect::>(), - 5, - CumulativePropagatorOptions::default(), - constraint_tag, - ), - ) + .new_propagator(TimeTablePerPointIncrementalConstructor::< + DomainId, + i32, + i32, + i32, + false, + >::new( + [ + ArgTask { + start_time: a, + processing_time: 2, + resource_usage: 1, + }, + ArgTask { + start_time: b1, + processing_time: 2, + resource_usage: 2, + }, + ArgTask { + start_time: b2, + processing_time: 3, + resource_usage: 2, + }, + ArgTask { + start_time: c, + processing_time: 2, + resource_usage: 4, + }, + ArgTask { + start_time: d, + processing_time: 2, + resource_usage: 2, + }, + ArgTask { + start_time: e, + processing_time: 4, + resource_usage: 2, + }, + ArgTask { + start_time: f, + processing_time: 6, + resource_usage: 2, + }, + ] + .into_iter() + .collect::>(), + 5, + CumulativePropagatorOptions::default(), + constraint_tag, + )) .expect("No conflict"); assert_eq!(solver.lower_bound(a), 0); assert_eq!(solver.upper_bound(a), 1); @@ -1077,30 +1160,34 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let _ = solver - .new_propagator( - TimeTablePerPointIncrementalPropagator::::new( - &[ - ArgTask { - start_time: s1, - processing_time: 4, - resource_usage: 1, - }, - ArgTask { - start_time: s2, - processing_time: 3, - resource_usage: 1, - }, - ] - .into_iter() - .collect::>(), - 1, - CumulativePropagatorOptions { - explanation_type: CumulativeExplanationType::Naive, - ..Default::default() - }, - constraint_tag, - ), - ) + .new_propagator(TimeTablePerPointIncrementalConstructor::< + DomainId, + i32, + i32, + i32, + false, + >::new( + [ + ArgTask { + start_time: s1, + processing_time: 4, + resource_usage: 1, + }, + ArgTask { + start_time: s2, + processing_time: 3, + resource_usage: 1, + }, + ] + .into_iter() + .collect::>(), + 1, + CumulativePropagatorOptions { + explanation_type: CumulativeExplanationType::Naive, + ..Default::default() + }, + constraint_tag, + )) .expect("No conflict"); assert_eq!(solver.lower_bound(s2), 5); assert_eq!(solver.upper_bound(s2), 8); @@ -1130,35 +1217,39 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let _ = solver - .new_propagator( - TimeTablePerPointIncrementalPropagator::::new( - &[ - ArgTask { - start_time: s1, - processing_time: 2, - resource_usage: 1, - }, - ArgTask { - start_time: s2, - processing_time: 2, - resource_usage: 1, - }, - ArgTask { - start_time: s3, - processing_time: 4, - resource_usage: 1, - }, - ] - .into_iter() - .collect::>(), - 1, - CumulativePropagatorOptions { - explanation_type: CumulativeExplanationType::Naive, - ..Default::default() - }, - constraint_tag, - ), - ) + .new_propagator(TimeTablePerPointIncrementalConstructor::< + DomainId, + i32, + i32, + i32, + false, + >::new( + [ + ArgTask { + start_time: s1, + processing_time: 2, + resource_usage: 1, + }, + ArgTask { + start_time: s2, + processing_time: 2, + resource_usage: 1, + }, + ArgTask { + start_time: s3, + processing_time: 4, + resource_usage: 1, + }, + ] + .into_iter() + .collect::>(), + 1, + CumulativePropagatorOptions { + explanation_type: CumulativeExplanationType::Naive, + ..Default::default() + }, + constraint_tag, + )) .expect("No conflict"); assert_eq!(solver.lower_bound(s3), 7); assert_eq!(solver.upper_bound(s3), 15); @@ -1186,31 +1277,35 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let _ = solver - .new_propagator( - TimeTablePerPointIncrementalPropagator::::new( - &[ - ArgTask { - start_time: s1, - processing_time: 4, - resource_usage: 1, - }, - ArgTask { - start_time: s2, - processing_time: 3, - resource_usage: 1, - }, - ] - .into_iter() - .collect::>(), - 1, - CumulativePropagatorOptions { - explanation_type: CumulativeExplanationType::Naive, - allow_holes_in_domain: true, - ..Default::default() - }, - constraint_tag, - ), - ) + .new_propagator(TimeTablePerPointIncrementalConstructor::< + DomainId, + i32, + i32, + i32, + false, + >::new( + [ + ArgTask { + start_time: s1, + processing_time: 4, + resource_usage: 1, + }, + ArgTask { + start_time: s2, + processing_time: 3, + resource_usage: 1, + }, + ] + .into_iter() + .collect::>(), + 1, + CumulativePropagatorOptions { + explanation_type: CumulativeExplanationType::Naive, + allow_holes_in_domain: true, + ..Default::default() + }, + constraint_tag, + )) .expect("No conflict"); assert_eq!(solver.lower_bound(s2), 0); assert_eq!(solver.upper_bound(s2), 8); @@ -1232,8 +1327,8 @@ mod tests { let s3_scratch = solver_scratch.new_variable(1, 10); let constraint_tag = solver_scratch.new_constraint_tag(); let propagator_scratch = solver_scratch - .new_propagator(TimeTablePerPointPropagator::new( - &[ + .new_propagator(TimeTablePerPointConstructor::new( + [ ArgTask { start_time: s1_scratch, processing_time: 2, @@ -1272,35 +1367,39 @@ mod tests { let s3 = solver.new_variable(1, 10); let constraint_tag = solver.new_constraint_tag(); let propagator = solver - .new_propagator( - TimeTablePerPointIncrementalPropagator::::new( - &[ - ArgTask { - start_time: s1, - processing_time: 2, - resource_usage: 1, - }, - ArgTask { - start_time: s2, - processing_time: 4, - resource_usage: 1, - }, - ArgTask { - start_time: s3, - processing_time: 4, - resource_usage: 1, - }, - ] - .into_iter() - .collect::>(), - 1, - CumulativePropagatorOptions { - explanation_type: CumulativeExplanationType::Naive, - ..Default::default() - }, - constraint_tag, - ), - ) + .new_propagator(TimeTablePerPointIncrementalConstructor::< + DomainId, + i32, + i32, + i32, + true, + >::new( + [ + ArgTask { + start_time: s1, + processing_time: 2, + resource_usage: 1, + }, + ArgTask { + start_time: s2, + processing_time: 4, + resource_usage: 1, + }, + ArgTask { + start_time: s3, + processing_time: 4, + resource_usage: 1, + }, + ] + .into_iter() + .collect::>(), + 1, + CumulativePropagatorOptions { + explanation_type: CumulativeExplanationType::Naive, + ..Default::default() + }, + constraint_tag, + )) .expect("No conflict"); let _ = solver.increase_lower_bound_and_notify(propagator, 2, s3, 7); let _ = solver.increase_lower_bound_and_notify(propagator, 1, s2, 7); @@ -1333,8 +1432,8 @@ mod tests { let s3_scratch = solver_scratch.new_variable(1, 10); let constraint_tag = solver_scratch.new_constraint_tag(); let propagator_scratch = solver_scratch - .new_propagator(TimeTablePerPointPropagator::new( - &[ + .new_propagator(TimeTablePerPointConstructor::new( + [ ArgTask { start_time: s1_scratch, processing_time: 2, @@ -1372,35 +1471,39 @@ mod tests { let s3 = solver.new_variable(1, 10); let constraint_tag = solver.new_constraint_tag(); let propagator = solver - .new_propagator( - TimeTablePerPointIncrementalPropagator::::new( - &[ - ArgTask { - start_time: s1, - processing_time: 2, - resource_usage: 1, - }, - ArgTask { - start_time: s2, - processing_time: 4, - resource_usage: 1, - }, - ArgTask { - start_time: s3, - processing_time: 4, - resource_usage: 1, - }, - ] - .into_iter() - .collect::>(), - 1, - CumulativePropagatorOptions { - explanation_type: CumulativeExplanationType::Naive, - ..Default::default() - }, - constraint_tag, - ), - ) + .new_propagator(TimeTablePerPointIncrementalConstructor::< + DomainId, + i32, + i32, + i32, + true, + >::new( + [ + ArgTask { + start_time: s1, + processing_time: 2, + resource_usage: 1, + }, + ArgTask { + start_time: s2, + processing_time: 4, + resource_usage: 1, + }, + ArgTask { + start_time: s3, + processing_time: 4, + resource_usage: 1, + }, + ] + .into_iter() + .collect::>(), + 1, + CumulativePropagatorOptions { + explanation_type: CumulativeExplanationType::Naive, + ..Default::default() + }, + constraint_tag, + )) .expect("No conflict"); let _ = solver.increase_lower_bound_and_notify(propagator, 2, s3, 7); let _ = solver.increase_lower_bound_and_notify(propagator, 1, s2, 7); @@ -1408,19 +1511,16 @@ mod tests { assert!(result.is_err()); let result_scratch = solver_scratch.propagate(propagator_scratch); assert!(result_scratch.is_err()); - assert!({ - let same = if let Err(Inconsistency::Conflict(explanation)) = &result { - if let Err(Inconsistency::Conflict(explanation_scratch)) = &result_scratch { - explanation.conjunction.iter().collect::>() - == explanation_scratch.conjunction.iter().collect::>() - } else { - false - } + + if let Err(Inconsistency::Conflict(explanation)) = &result { + if let Err(Inconsistency::Conflict(explanation_scratch)) = &result_scratch { + assert_eq!(explanation.conjunction, explanation_scratch.conjunction) } else { - false - }; - same - }); + panic!("Synchronised version found a conflict while the non-synchronised version did not"); + } + } else { + panic!("Synchronised version did not find a conflict"); + } } #[test] @@ -1431,8 +1531,8 @@ mod tests { let s3_scratch = solver_scratch.new_variable(1, 10); let constraint_tag = solver_scratch.new_constraint_tag(); let propagator_scratch = solver_scratch - .new_propagator(TimeTablePerPointPropagator::new( - &[ + .new_propagator(TimeTablePerPointConstructor::new( + [ ArgTask { start_time: s1_scratch, processing_time: 2, @@ -1470,35 +1570,39 @@ mod tests { let s3 = solver.new_variable(1, 10); let constraint_tag = solver.new_constraint_tag(); let propagator = solver - .new_propagator( - TimeTablePerPointIncrementalPropagator::::new( - &[ - ArgTask { - start_time: s1, - processing_time: 2, - resource_usage: 1, - }, - ArgTask { - start_time: s2, - processing_time: 4, - resource_usage: 1, - }, - ArgTask { - start_time: s3, - processing_time: 4, - resource_usage: 1, - }, - ] - .into_iter() - .collect::>(), - 1, - CumulativePropagatorOptions { - explanation_type: CumulativeExplanationType::Naive, - ..Default::default() - }, - constraint_tag, - ), - ) + .new_propagator(TimeTablePerPointIncrementalConstructor::< + DomainId, + i32, + i32, + i32, + false, + >::new( + [ + ArgTask { + start_time: s1, + processing_time: 2, + resource_usage: 1, + }, + ArgTask { + start_time: s2, + processing_time: 4, + resource_usage: 1, + }, + ArgTask { + start_time: s3, + processing_time: 4, + resource_usage: 1, + }, + ] + .into_iter() + .collect::>(), + 1, + CumulativePropagatorOptions { + explanation_type: CumulativeExplanationType::Naive, + ..Default::default() + }, + constraint_tag, + )) .expect("No conflict"); let _ = solver.increase_lower_bound_and_notify(propagator, 2, s3, 7); let _ = solver.increase_lower_bound_and_notify(propagator, 1, s2, 7); @@ -1527,8 +1631,8 @@ mod tests { let s3_scratch = solver_scratch.new_variable(5, 11); let constraint_tag = solver_scratch.new_constraint_tag(); let propagator_scratch = solver_scratch - .new_propagator(TimeTablePerPointPropagator::new( - &[ + .new_propagator(TimeTablePerPointConstructor::new( + [ ArgTask { start_time: s1_scratch, processing_time: 2, @@ -1566,35 +1670,39 @@ mod tests { let s3 = solver.new_variable(5, 11); let constraint_tag = solver.new_constraint_tag(); let propagator = solver - .new_propagator( - TimeTablePerPointIncrementalPropagator::::new( - &[ - ArgTask { - start_time: s1, - processing_time: 2, - resource_usage: 1, - }, - ArgTask { - start_time: s2, - processing_time: 4, - resource_usage: 1, - }, - ArgTask { - start_time: s3, - processing_time: 4, - resource_usage: 1, - }, - ] - .into_iter() - .collect::>(), - 2, - CumulativePropagatorOptions { - explanation_type: CumulativeExplanationType::Naive, - ..Default::default() - }, - constraint_tag, - ), - ) + .new_propagator(TimeTablePerPointIncrementalConstructor::< + DomainId, + i32, + i32, + i32, + true, + >::new( + [ + ArgTask { + start_time: s1, + processing_time: 2, + resource_usage: 1, + }, + ArgTask { + start_time: s2, + processing_time: 4, + resource_usage: 1, + }, + ArgTask { + start_time: s3, + processing_time: 4, + resource_usage: 1, + }, + ] + .into_iter() + .collect::>(), + 2, + CumulativePropagatorOptions { + explanation_type: CumulativeExplanationType::Naive, + ..Default::default() + }, + constraint_tag, + )) .expect("No conflict"); let _ = solver.increase_lower_bound_and_notify(propagator, 1, s2, 5); let result = solver.propagate(propagator); @@ -1621,8 +1729,8 @@ mod tests { let s3_scratch = solver_scratch.new_variable(5, 11); let constraint_tag = solver_scratch.new_constraint_tag(); let propagator_scratch = solver_scratch - .new_propagator(TimeTablePerPointPropagator::new( - &[ + .new_propagator(TimeTablePerPointConstructor::new( + [ ArgTask { start_time: s1_scratch, processing_time: 2, @@ -1659,35 +1767,39 @@ mod tests { let s3 = solver.new_variable(5, 11); let constraint_tag = solver.new_constraint_tag(); let propagator = solver - .new_propagator( - TimeTablePerPointIncrementalPropagator::::new( - &[ - ArgTask { - start_time: s1, - processing_time: 2, - resource_usage: 1, - }, - ArgTask { - start_time: s2, - processing_time: 4, - resource_usage: 1, - }, - ArgTask { - start_time: s3, - processing_time: 4, - resource_usage: 1, - }, - ] - .into_iter() - .collect::>(), - 2, - CumulativePropagatorOptions { - explanation_type: CumulativeExplanationType::Naive, - ..Default::default() - }, - constraint_tag, - ), - ) + .new_propagator(TimeTablePerPointIncrementalConstructor::< + DomainId, + i32, + i32, + i32, + false, + >::new( + [ + ArgTask { + start_time: s1, + processing_time: 2, + resource_usage: 1, + }, + ArgTask { + start_time: s2, + processing_time: 4, + resource_usage: 1, + }, + ArgTask { + start_time: s3, + processing_time: 4, + resource_usage: 1, + }, + ] + .into_iter() + .collect::>(), + 2, + CumulativePropagatorOptions { + explanation_type: CumulativeExplanationType::Naive, + ..Default::default() + }, + constraint_tag, + )) .expect("No conflict"); let _ = solver.increase_lower_bound_and_notify(propagator, 1, s2, 5); let result = solver.propagate(propagator); @@ -1719,8 +1831,8 @@ mod tests { let constraint_tag = solver_scratch.new_constraint_tag(); let propagator_scratch = solver_scratch - .new_propagator(TimeTablePerPointPropagator::new( - &[ + .new_propagator(TimeTablePerPointConstructor::new( + [ ArgTask { start_time: s0_scratch, processing_time: 4, @@ -1763,35 +1875,39 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let propagator = solver - .new_propagator( - TimeTablePerPointIncrementalPropagator::::new( - &[ - ArgTask { - start_time: s0, - processing_time: 4, - resource_usage: 1, - }, - ArgTask { - start_time: s1, - processing_time: 1, - resource_usage: 1, - }, - ArgTask { - start_time: s2, - processing_time: 1, - resource_usage: 1, - }, - ] - .into_iter() - .collect::>(), - 1, - CumulativePropagatorOptions { - explanation_type: CumulativeExplanationType::Naive, - ..Default::default() - }, - constraint_tag, - ), - ) + .new_propagator(TimeTablePerPointIncrementalConstructor::< + DomainId, + i32, + i32, + i32, + false, + >::new( + [ + ArgTask { + start_time: s0, + processing_time: 4, + resource_usage: 1, + }, + ArgTask { + start_time: s1, + processing_time: 1, + resource_usage: 1, + }, + ArgTask { + start_time: s2, + processing_time: 1, + resource_usage: 1, + }, + ] + .into_iter() + .collect::>(), + 1, + CumulativePropagatorOptions { + explanation_type: CumulativeExplanationType::Naive, + ..Default::default() + }, + constraint_tag, + )) .expect("No conflict"); let _ = solver.increase_lower_bound_and_notify(propagator, 2, s2, 5); let _ = solver.increase_lower_bound_and_notify(propagator, 1, s1, 5); @@ -1834,8 +1950,8 @@ mod tests { let constraint_tag = solver_scratch.new_constraint_tag(); let propagator_scratch = solver_scratch - .new_propagator(TimeTablePerPointPropagator::new( - &[ + .new_propagator(TimeTablePerPointConstructor::new( + [ ArgTask { start_time: s0_scratch, processing_time: 4, @@ -1877,35 +1993,39 @@ mod tests { let s2 = solver.new_variable(1, 5); let propagator = solver - .new_propagator( - TimeTablePerPointIncrementalPropagator::::new( - &[ - ArgTask { - start_time: s0, - processing_time: 4, - resource_usage: 1, - }, - ArgTask { - start_time: s1, - processing_time: 1, - resource_usage: 1, - }, - ArgTask { - start_time: s2, - processing_time: 1, - resource_usage: 1, - }, - ] - .into_iter() - .collect::>(), - 1, - CumulativePropagatorOptions { - explanation_type: CumulativeExplanationType::Naive, - ..Default::default() - }, - constraint_tag, - ), - ) + .new_propagator(TimeTablePerPointIncrementalConstructor::< + DomainId, + i32, + i32, + i32, + true, + >::new( + [ + ArgTask { + start_time: s0, + processing_time: 4, + resource_usage: 1, + }, + ArgTask { + start_time: s1, + processing_time: 1, + resource_usage: 1, + }, + ArgTask { + start_time: s2, + processing_time: 1, + resource_usage: 1, + }, + ] + .into_iter() + .collect::>(), + 1, + CumulativePropagatorOptions { + explanation_type: CumulativeExplanationType::Naive, + ..Default::default() + }, + constraint_tag, + )) .expect("No conflict"); let _ = solver.increase_lower_bound_and_notify(propagator, 2, s2, 5); let _ = solver.increase_lower_bound_and_notify(propagator, 1, s1, 5); diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs index 1e1ae3acb..64e8d6df3 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs @@ -88,7 +88,7 @@ impl CumulativePropagationHandler { create_naive_propagation_explanation(profile, context.as_readonly()) } CumulativeExplanationType::BigStep => { - create_big_step_propagation_explanation(profile) + create_big_step_propagation_explanation(context.as_readonly(), profile) } CumulativeExplanationType::Pointwise => { unreachable!("At the moment, we do not store the profile explanation for the pointwise explanation since it consists of multiple explanations") @@ -156,7 +156,7 @@ impl CumulativePropagationHandler { create_naive_propagation_explanation(profile, context.as_readonly()) } CumulativeExplanationType::BigStep => { - create_big_step_propagation_explanation(profile) + create_big_step_propagation_explanation(context.as_readonly(), profile) } CumulativeExplanationType::Pointwise => { unreachable!("At the moment, we do not store the profile explanation for the pointwise explanation since it consists of multiple explanations") @@ -182,7 +182,8 @@ impl CumulativePropagationHandler { context.post( predicate![ propagating_task.start_variable - <= profiles[0].start - propagating_task.processing_time + <= profiles[0].start + - context.lower_bound(&propagating_task.processing_time) ], full_explanation, self.inference_code, @@ -260,10 +261,12 @@ impl CumulativePropagationHandler { ) -> Result<(), EmptyDomain> where Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, { pumpkin_assert_advanced!( context.upper_bound(&propagating_task.start_variable) - > profile.start - propagating_task.processing_time + > profile.start - context.lower_bound(&propagating_task.processing_time) ); match self.explanation_type { @@ -288,7 +291,8 @@ impl CumulativePropagationHandler { context.post( predicate![ propagating_task.start_variable - <= profile.start - propagating_task.processing_time + <= profile.start + - context.lower_bound(&propagating_task.processing_time) ], reason, self.inference_code, @@ -329,7 +333,7 @@ impl CumulativePropagationHandler { // time-point in which case we simply start from the lower-bound of the task. let lower_bound_removed_time_points = max( context.lower_bound(&propagating_task.start_variable), - profile.start - propagating_task.processing_time + 1, + profile.start - context.lower_bound(&propagating_task.processing_time) + 1, ); // There are also two options for determine the highest value to remove @@ -375,7 +379,7 @@ impl CumulativePropagationHandler { // together with the propagating task would overflow the capacity) let corresponding_profile_explanation_point = if time_point < profile.start { min( - time_point + propagating_task.processing_time - 1, + time_point + context.lower_bound(&propagating_task.processing_time) - 1, (profile.end - profile.start) / 2 + profile.start, ) } else { @@ -383,6 +387,7 @@ impl CumulativePropagationHandler { }; let explanation = create_pointwise_propagation_explanation( + context.as_readonly(), corresponding_profile_explanation_point, profile, ); @@ -423,7 +428,7 @@ impl CumulativePropagationHandler { create_naive_propagation_explanation(profile, context.as_readonly()) }, CumulativeExplanationType::BigStep => { - create_big_step_propagation_explanation(profile) + create_big_step_propagation_explanation(context.as_readonly(), profile) }, CumulativeExplanationType::Pointwise => { unreachable!("At the moment, we do not store the profile explanation for the pointwise explanation since it consists of multiple explanations") @@ -436,8 +441,8 @@ impl CumulativePropagationHandler { /// Creates an explanation of the conflict caused by `conflict_profile` based on the provided /// `explanation_type`. -pub(crate) fn create_conflict_explanation( - context: Context, +pub(crate) fn create_conflict_explanation( + context: PropagationContext, inference_code: InferenceCode, conflict_profile: &ResourceProfile, explanation_type: CumulativeExplanationType, @@ -452,10 +457,10 @@ where create_naive_conflict_explanation(conflict_profile, context) } CumulativeExplanationType::BigStep => { - create_big_step_conflict_explanation(conflict_profile) + create_big_step_conflict_explanation(context, conflict_profile) } CumulativeExplanationType::Pointwise => { - create_pointwise_conflict_explanation(conflict_profile) + create_pointwise_conflict_explanation(context, conflict_profile) } }; diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_over_interval.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_over_interval.rs index 42aaf17de..737bba704 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_over_interval.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_over_interval.rs @@ -68,9 +68,52 @@ pub(crate) struct TimeTableOverIntervalPropagator { /// Stores structures which change during the search; used to store the bounds updatable_structures: UpdatableStructures, - // TODO: Update with propagator constructor. + inference_code: InferenceCode, +} + +pub(crate) struct TimeTableOverIntervalConstructor { + tasks: Vec>, + capacity: CVar, + cumulative_options: CumulativePropagatorOptions, constraint_tag: ConstraintTag, - inference_code: Option, +} + +impl TimeTableOverIntervalConstructor { + pub(crate) fn new( + arg_tasks: Vec>, + capacity: CVar, + cumulative_options: CumulativePropagatorOptions, + constraint_tag: ConstraintTag, + ) -> Self { + Self { + tasks: arg_tasks, + capacity, + cumulative_options, + constraint_tag, + } + } +} + +impl< + Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, + > PropagatorConstructor for TimeTableOverIntervalConstructor +{ + type PropagatorImpl = TimeTableOverIntervalPropagator; + + fn create(self, mut context: PropagatorConstructorContext) -> Self::PropagatorImpl { + let inference_code = context.create_inference_code(self.constraint_tag, TimeTable); + + TimeTableOverIntervalPropagator::new( + context, + &self.tasks, + self.capacity, + self.cumulative_options, + inference_code, + ) + } } /// The type of the time-table used by propagators which use time-table reasoning over intervals. @@ -86,46 +129,31 @@ impl< CVar: IntegerVariable + 'static, > TimeTableOverIntervalPropagator { - pub(crate) fn new( + fn new( + mut context: PropagatorConstructorContext, arg_tasks: &[ArgTask], capacity: CVar, cumulative_options: CumulativePropagatorOptions, - constraint_tag: ConstraintTag, + inference_code: InferenceCode, ) -> TimeTableOverIntervalPropagator { - let tasks = create_tasks(arg_tasks); - let parameters = CumulativeParameters::new(tasks, capacity, cumulative_options); - let updatable_structures = UpdatableStructures::new(¶meters); + let tasks = create_tasks(context.as_readonly(), arg_tasks); + + let parameters = + CumulativeParameters::new(context.as_readonly(), tasks, capacity, cumulative_options); + register_tasks(¶meters.tasks, context.reborrow(), false); + + let mut updatable_structures = UpdatableStructures::new(¶meters); + updatable_structures.initialise_bounds_and_remove_fixed(context.as_readonly(), ¶meters); TimeTableOverIntervalPropagator { is_time_table_empty: true, parameters, updatable_structures, - constraint_tag, - inference_code: None, + inference_code, } } } -impl< - Var: IntegerVariable + 'static, - PVar: IntegerVariable + 'static, - RVar: IntegerVariable + 'static, - CVar: IntegerVariable + 'static, - > PropagatorConstructor for TimeTableOverIntervalPropagator -{ - type PropagatorImpl = Self; - - fn create(mut self, mut context: PropagatorConstructorContext) -> Self::PropagatorImpl { - self.updatable_structures - .initialise_bounds_and_remove_fixed(context.as_readonly(), &self.parameters); - register_tasks(&self.parameters.tasks, context.reborrow(), false); - - self.inference_code = Some(context.create_inference_code(self.constraint_tag, TimeTable)); - - self - } -} - impl< Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, @@ -137,21 +165,21 @@ impl< if self.parameters.is_infeasible { return Err(Inconsistency::Conflict(PropagatorConflict { conjunction: conjunction!(), - inference_code: self.inference_code.unwrap(), + inference_code: self.inference_code, })); } let time_table = create_time_table_over_interval_from_scratch( context.as_readonly(), &self.parameters, - self.inference_code.unwrap(), + self.inference_code, )?; self.is_time_table_empty = time_table.is_empty(); // No error has been found -> Check for updates (i.e. go over all profiles and all tasks and // check whether an update can take place) propagate_based_on_timetable( &mut context, - self.inference_code.unwrap(), + self.inference_code, time_table.iter(), &self.parameters, &mut self.updatable_structures, @@ -215,7 +243,7 @@ impl< &mut context, &self.parameters, &self.updatable_structures, - self.inference_code.unwrap(), + self.inference_code, ) } } @@ -235,9 +263,8 @@ pub(crate) fn create_time_table_over_interval_from_scratch< PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, CVar: IntegerVariable + 'static, - Context: ReadDomains + Copy, >( - context: Context, + context: PropagationContext, parameters: &CumulativeParameters, inference_code: InferenceCode, ) -> Result, PropagatorConflict> { @@ -271,7 +298,7 @@ fn create_events< for task in parameters.tasks.iter() { let upper_bound = context.upper_bound(&task.start_variable); let lower_bound = context.lower_bound(&task.start_variable); - if upper_bound < lower_bound + task.processing_time { + if upper_bound < lower_bound + context.lower_bound(&task.processing_time) { // The task has a mandatory part, we need to add the appropriate events to the // events list @@ -279,15 +306,15 @@ fn create_events< // resource usage) events.push(Event { time_stamp: upper_bound, - change_in_resource_usage: task.resource_usage, + change_in_resource_usage: context.lower_bound(&task.resource_usage), task: Rc::clone(task), }); // Then we create an event for the end of a mandatory part (with negative resource // usage) events.push(Event { - time_stamp: lower_bound + task.processing_time, - change_in_resource_usage: -task.resource_usage, + time_stamp: lower_bound + context.lower_bound(&task.processing_time), + change_in_resource_usage: -context.lower_bound(&task.resource_usage), task: Rc::clone(task), }); } @@ -326,10 +353,9 @@ fn create_time_table_from_events< PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, CVar: IntegerVariable + 'static, - Context: ReadDomains + Copy, >( events: Vec>, - context: Context, + context: PropagationContext, inference_code: InferenceCode, parameters: &CumulativeParameters, ) -> Result, PropagatorConflict> { @@ -348,7 +374,7 @@ fn create_time_table_from_events< "Events were not ordered in such a way that the ends of mandatory parts occurred first" ); - let mut time_table: OverIntervalTimeTableType = Default::default(); + let mut time_table: OverIntervalTimeTableType = Default::default(); // The tasks which are contributing to the current profile under consideration let mut current_profile_tasks: Vec>> = Vec::new(); // The cumulative resource usage of the tasks which are contributing to the current profile @@ -382,7 +408,7 @@ fn create_time_table_from_events< // We have first traversed all of the ends of mandatory parts, meaning that any // overflow will persist after processing all events at this time-point - if current_resource_usage > parameters.capacity { + if current_resource_usage > context.upper_bound(¶meters.capacity) { is_conflicting = true; } @@ -514,14 +540,14 @@ pub(crate) fn debug_propagate_from_scratch_time_table_interval< mod tests { use crate::basic_types::Inconsistency; use crate::conjunction; + use crate::constraint_arguments::CumulativeExplanationType; use crate::engine::predicates::predicate::Predicate; use crate::engine::propagation::EnqueueDecision; use crate::engine::test_solver::TestSolver; - use crate::options::CumulativeExplanationType; use crate::predicate; use crate::propagators::ArgTask; use crate::propagators::CumulativePropagatorOptions; - use crate::propagators::TimeTableOverIntervalPropagator; + use crate::propagators::TimeTableOverIntervalConstructor; #[test] fn propagator_propagates_from_profile() { @@ -531,8 +557,8 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let _ = solver - .new_propagator(TimeTableOverIntervalPropagator::new( - &[ + .new_propagator(TimeTableOverIntervalConstructor::new( + [ ArgTask { start_time: s1, processing_time: 4, @@ -564,8 +590,8 @@ mod tests { let s2 = solver.new_variable(1, 1); let constraint_tag = solver.new_constraint_tag(); - let result = solver.new_propagator(TimeTableOverIntervalPropagator::new( - &[ + let result = solver.new_propagator(TimeTableOverIntervalConstructor::new( + [ ArgTask { start_time: s1, processing_time: 4, @@ -619,8 +645,8 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let _ = solver - .new_propagator(TimeTableOverIntervalPropagator::new( - &[ + .new_propagator(TimeTableOverIntervalConstructor::new( + [ ArgTask { start_time: s1, processing_time: 4, @@ -657,8 +683,8 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let _ = solver - .new_propagator(TimeTableOverIntervalPropagator::new( - &[ + .new_propagator(TimeTableOverIntervalConstructor::new( + [ ArgTask { start_time: a, processing_time: 2, @@ -708,8 +734,8 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let propagator = solver - .new_propagator(TimeTableOverIntervalPropagator::new( - &[ + .new_propagator(TimeTableOverIntervalConstructor::new( + [ ArgTask { start_time: s1, processing_time: 2, @@ -754,8 +780,8 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let _ = solver - .new_propagator(TimeTableOverIntervalPropagator::new( - &[ + .new_propagator(TimeTableOverIntervalConstructor::new( + [ ArgTask { start_time: s1, processing_time: 4, @@ -798,8 +824,8 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let propagator = solver - .new_propagator(TimeTableOverIntervalPropagator::new( - &[ + .new_propagator(TimeTableOverIntervalConstructor::new( + [ ArgTask { start_time: a, processing_time: 2, @@ -875,8 +901,8 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let propagator = solver - .new_propagator(TimeTableOverIntervalPropagator::new( - &[ + .new_propagator(TimeTableOverIntervalConstructor::new( + [ ArgTask { start_time: a, processing_time: 2, @@ -949,8 +975,8 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let _ = solver - .new_propagator(TimeTableOverIntervalPropagator::new( - &[ + .new_propagator(TimeTableOverIntervalConstructor::new( + [ ArgTask { start_time: s1, processing_time: 4, @@ -990,8 +1016,8 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let _ = solver - .new_propagator(TimeTableOverIntervalPropagator::new( - &[ + .new_propagator(TimeTableOverIntervalConstructor::new( + [ ArgTask { start_time: s1, processing_time: 2, @@ -1037,8 +1063,8 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let _ = solver - .new_propagator(TimeTableOverIntervalPropagator::new( - &[ + .new_propagator(TimeTableOverIntervalConstructor::new( + [ ArgTask { start_time: s1, processing_time: 4, diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_per_point.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_per_point.rs index 0ef81ce55..d4ac5bf32 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_per_point.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_per_point.rs @@ -50,7 +50,6 @@ use crate::pumpkin_assert_extreme; /// \[1\] A. Schutt, Improving scheduling by learning. University of Melbourne, Department of /// Computer Science and Software Engineering, 2011. #[derive(Debug)] - pub(crate) struct TimeTablePerPointPropagator { /// Stores whether the time-table is empty is_time_table_empty: bool, @@ -59,9 +58,7 @@ pub(crate) struct TimeTablePerPointPropagator { /// Stores structures which change during the search; used to store the bounds updatable_structures: UpdatableStructures, - // TODO: Update with proapgator constructor. - constraint_tag: ConstraintTag, - inference_code: Option, + inference_code: InferenceCode, } /// The type of the time-table used by propagators which use time-table reasoning per time-point; @@ -74,30 +71,48 @@ pub(crate) struct TimeTablePerPointPropagator { pub(crate) type PerPointTimeTableType = BTreeMap>; +pub(crate) struct TimeTablePerPointConstructor { + tasks: Vec>, + capacity: CVar, + cumulative_options: CumulativePropagatorOptions, + constraint_tag: ConstraintTag, +} + +impl TimeTablePerPointConstructor { + pub(crate) fn new( + arg_tasks: Vec>, + capacity: CVar, + cumulative_options: CumulativePropagatorOptions, + constraint_tag: ConstraintTag, + ) -> Self { + Self { + tasks: arg_tasks, + capacity, + cumulative_options, + constraint_tag, + } + } +} + impl< Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, CVar: IntegerVariable + 'static, - > TimeTablePerPointPropagator + > PropagatorConstructor for TimeTablePerPointConstructor { - pub(crate) fn new( - arg_tasks: &[ArgTask], - capacity: CVar, - cumulative_options: CumulativePropagatorOptions, - constraint_tag: ConstraintTag, - ) -> TimeTablePerPointPropagator { - let tasks = create_tasks(arg_tasks); - let parameters = CumulativeParameters::new(tasks, capacity, cumulative_options); - let updatable_structures = UpdatableStructures::new(¶meters); + type PropagatorImpl = TimeTablePerPointPropagator; - TimeTablePerPointPropagator { - is_time_table_empty: true, - parameters, - updatable_structures, - constraint_tag, - inference_code: None, - } + fn create(self, mut context: PropagatorConstructorContext) -> Self::PropagatorImpl { + let inference_code = context.create_inference_code(self.constraint_tag, TimeTable); + + TimeTablePerPointPropagator::new( + context, + &self.tasks, + self.capacity, + self.cumulative_options, + inference_code, + ) } } @@ -106,18 +121,30 @@ impl< PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, CVar: IntegerVariable + 'static, - > PropagatorConstructor for TimeTablePerPointPropagator + > TimeTablePerPointPropagator { - type PropagatorImpl = Self; + fn new( + mut context: PropagatorConstructorContext, + arg_tasks: &[ArgTask], + capacity: CVar, + cumulative_options: CumulativePropagatorOptions, + inference_code: InferenceCode, + ) -> TimeTablePerPointPropagator { + let tasks = create_tasks(context.as_readonly(), arg_tasks); - fn create(mut self, mut context: PropagatorConstructorContext) -> Self::PropagatorImpl { - self.updatable_structures - .initialise_bounds_and_remove_fixed(context.as_readonly(), &self.parameters); - register_tasks(&self.parameters.tasks, context.reborrow(), false); + let parameters = + CumulativeParameters::new(context.as_readonly(), tasks, capacity, cumulative_options); + register_tasks(¶meters.tasks, context.reborrow(), false); - self.inference_code = Some(context.create_inference_code(self.constraint_tag, TimeTable)); + let mut updatable_structures = UpdatableStructures::new(¶meters); + updatable_structures.initialise_bounds_and_remove_fixed(context.as_readonly(), ¶meters); - self + TimeTablePerPointPropagator { + is_time_table_empty: true, + parameters, + updatable_structures, + inference_code, + } } } @@ -132,13 +159,13 @@ impl< if self.parameters.is_infeasible { return Err(Inconsistency::Conflict(PropagatorConflict { conjunction: conjunction!(), - inference_code: self.inference_code.unwrap(), + inference_code: self.inference_code, })); } let time_table = create_time_table_per_point_from_scratch( context.as_readonly(), - self.inference_code.unwrap(), + self.inference_code, &self.parameters, )?; self.is_time_table_empty = time_table.is_empty(); @@ -146,7 +173,7 @@ impl< // check whether an update can take place) propagate_based_on_timetable( &mut context, - self.inference_code.unwrap(), + self.inference_code, time_table.values(), &self.parameters, &mut self.updatable_structures, @@ -210,7 +237,7 @@ impl< ) -> PropagationStatusCP { debug_propagate_from_scratch_time_table_point( &mut context, - self.inference_code.unwrap(), + self.inference_code, &self.parameters, &self.updatable_structures, ) @@ -230,9 +257,8 @@ pub(crate) fn create_time_table_per_point_from_scratch< PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, CVar: IntegerVariable + 'static, - Context: ReadDomains + Copy, >( - context: Context, + context: PropagationContext, inference_code: InferenceCode, parameters: &CumulativeParameters, ) -> Result, PropagatorConflict> { @@ -242,19 +268,19 @@ pub(crate) fn create_time_table_per_point_from_scratch< let upper_bound = context.upper_bound(&task.start_variable); let lower_bound = context.lower_bound(&task.start_variable); - if upper_bound < lower_bound + task.processing_time { + if upper_bound < lower_bound + context.lower_bound(&task.processing_time) { // There is a mandatory part - for i in upper_bound..(lower_bound + task.processing_time) { + for i in upper_bound..(lower_bound + context.lower_bound(&task.processing_time)) { // For every time-point of the mandatory part, // add the resource usage of the current task to the ResourceProfile and add it // to the profile tasks of the resource let current_profile: &mut ResourceProfile = time_table .entry(i as u32) .or_insert(ResourceProfile::default(i)); - current_profile.height += task.resource_usage; + current_profile.height += context.lower_bound(&task.resource_usage); current_profile.profile_tasks.push(Rc::clone(task)); - if current_profile.height > parameters.capacity { + if current_profile.height > context.upper_bound(¶meters.capacity) { // The addition of the current task to the resource profile has caused an // overflow return Err(create_conflict_explanation( @@ -308,14 +334,14 @@ pub(crate) fn debug_propagate_from_scratch_time_table_point< mod tests { use crate::basic_types::Inconsistency; use crate::conjunction; + use crate::constraint_arguments::CumulativeExplanationType; use crate::engine::predicates::predicate::Predicate; use crate::engine::propagation::EnqueueDecision; use crate::engine::test_solver::TestSolver; - use crate::options::CumulativeExplanationType; use crate::predicate; use crate::propagators::ArgTask; use crate::propagators::CumulativePropagatorOptions; - use crate::propagators::TimeTablePerPointPropagator; + use crate::propagators::TimeTablePerPointConstructor; #[test] fn propagator_propagates_from_profile() { @@ -325,8 +351,8 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let _ = solver - .new_propagator(TimeTablePerPointPropagator::new( - &[ + .new_propagator(TimeTablePerPointConstructor::new( + [ ArgTask { start_time: s1, processing_time: 4, @@ -358,8 +384,8 @@ mod tests { let s2 = solver.new_variable(1, 1); let constraint_tag = solver.new_constraint_tag(); - let result = solver.new_propagator(TimeTablePerPointPropagator::new( - &[ + let result = solver.new_propagator(TimeTablePerPointConstructor::new( + [ ArgTask { start_time: s1, processing_time: 4, @@ -411,8 +437,8 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let _ = solver - .new_propagator(TimeTablePerPointPropagator::new( - &[ + .new_propagator(TimeTablePerPointConstructor::new( + [ ArgTask { start_time: s1, processing_time: 4, @@ -449,8 +475,8 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let _ = solver - .new_propagator(TimeTablePerPointPropagator::new( - &[ + .new_propagator(TimeTablePerPointConstructor::new( + [ ArgTask { start_time: a, processing_time: 2, @@ -500,8 +526,8 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let propagator = solver - .new_propagator(TimeTablePerPointPropagator::new( - &[ + .new_propagator(TimeTablePerPointConstructor::new( + [ ArgTask { start_time: s1, processing_time: 2, @@ -546,8 +572,8 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let propagator = solver - .new_propagator(TimeTablePerPointPropagator::new( - &[ + .new_propagator(TimeTablePerPointConstructor::new( + [ ArgTask { start_time: s1, processing_time: 4, @@ -592,8 +618,8 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let propagator = solver - .new_propagator(TimeTablePerPointPropagator::new( - &[ + .new_propagator(TimeTablePerPointConstructor::new( + [ ArgTask { start_time: a, processing_time: 2, @@ -668,8 +694,8 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let propagator = solver - .new_propagator(TimeTablePerPointPropagator::new( - &[ + .new_propagator(TimeTablePerPointConstructor::new( + [ ArgTask { start_time: a, processing_time: 2, @@ -742,8 +768,8 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let _ = solver - .new_propagator(TimeTablePerPointPropagator::new( - &[ + .new_propagator(TimeTablePerPointConstructor::new( + [ ArgTask { start_time: s1, processing_time: 4, @@ -793,8 +819,8 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let _ = solver - .new_propagator(TimeTablePerPointPropagator::new( - &[ + .new_propagator(TimeTablePerPointConstructor::new( + [ ArgTask { start_time: s1, processing_time: 2, @@ -847,8 +873,8 @@ mod tests { let constraint_tag = solver.new_constraint_tag(); let _ = solver - .new_propagator(TimeTablePerPointPropagator::new( - &[ + .new_propagator(TimeTablePerPointConstructor::new( + [ ArgTask { start_time: s1, processing_time: 4, diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_util.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_util.rs index 6887b7855..3682a5691 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_util.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_util.rs @@ -57,7 +57,11 @@ pub(crate) fn should_enqueue< 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" + , "Either the stored lower-bound ({}) was larger than or equal to the actual lower bound ({}) or the stored 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", + updatable_structures.get_stored_lower_bound(updated_task), + context.lower_bound(&updated_task.start_variable), + updatable_structures.get_stored_upper_bound(updated_task), + context.upper_bound(&updated_task.start_variable), ); let mut result = ShouldEnqueueResult { @@ -119,7 +123,7 @@ pub(crate) fn has_mandatory_part< task: &Rc>, ) -> bool { context.upper_bound(&task.start_variable) - < context.lower_bound(&task.start_variable) + task.processing_time + < context.lower_bound(&task.start_variable) + context.lower_bound(&task.processing_time) } /// Checks whether a specific task (indicated by id) has a mandatory part which overlaps with the @@ -139,8 +143,13 @@ pub(crate) fn has_mandatory_part_in_interval< context.upper_bound(&task.start_variable), ); // There exists a mandatory part - (upper_bound < (lower_bound + task.processing_time)) - && has_overlap_with_interval(upper_bound, lower_bound + task.processing_time, start, end) + (upper_bound < (lower_bound + context.lower_bound(&task.processing_time))) + && has_overlap_with_interval( + upper_bound, + lower_bound + context.lower_bound(&task.processing_time), + start, + end, + ) // Determine whether the mandatory part overlaps with the provided bounds } @@ -157,7 +166,7 @@ pub(crate) fn task_has_overlap_with_interval< ) -> bool { let (lower_bound, upper_bound) = ( context.lower_bound(&task.start_variable), - context.upper_bound(&task.start_variable) + task.processing_time, + context.upper_bound(&task.start_variable) + context.lower_bound(&task.processing_time), ); // The release time of the task and the deadline has_overlap_with_interval(lower_bound, upper_bound, start, end) } @@ -323,7 +332,10 @@ fn propagate_single_profiles< } continue; } - if profile.start > context.upper_bound(&task.start_variable) + task.processing_time { + if profile.start + > context.upper_bound(&task.start_variable) + + context.lower_bound(&task.processing_time) + { // The start of the current profile is necessarily after the latest // completion time of the task under consideration The profiles are // sorted by start time (and non-overlapping) so we can remove the task from @@ -404,7 +416,10 @@ fn propagate_sequence_of_profiles< 'profile_loop: while profile_index < time_table.len() { let profile = time_table[profile_index]; - if profile.start > context.upper_bound(&task.start_variable) + task.processing_time { + if profile.start + > context.upper_bound(&task.start_variable) + + context.lower_bound(&task.processing_time) + { // The profiles are sorted, if we cannot update using this one then we cannot update // using the subsequent profiles, we can break from the loop break 'profile_loop; @@ -429,7 +444,7 @@ fn propagate_sequence_of_profiles< context.as_readonly(), task, profile, - parameters.capacity, + context.upper_bound(¶meters.capacity), ) { // We find the index (non-inclusive) of the last profile in the chain of lower-bound // propagations @@ -438,7 +453,7 @@ fn propagate_sequence_of_profiles< &time_table, context.as_readonly(), task, - parameters.capacity, + context.upper_bound(¶meters.capacity), ); // Then we provide the propagation handler with the chain of profiles and propagate @@ -458,7 +473,7 @@ fn propagate_sequence_of_profiles< context.as_readonly(), task, profile, - parameters.capacity, + context.upper_bound(¶meters.capacity), ) { // We find the index (inclusive) of the last profile in the chain of upper-bound // propagations (note that the index of this last profile in the chain is `<= @@ -468,7 +483,7 @@ fn propagate_sequence_of_profiles< &time_table, context.as_readonly(), task, - parameters.capacity, + context.upper_bound(¶meters.capacity), ); // Then we provide the propagation handler with the chain of profiles and propagate // all of them @@ -516,7 +531,8 @@ fn find_index_last_profile_which_propagates_lower_bound< let mut last_index = profile_index + 1; while last_index < time_table.len() { let next_profile = time_table[last_index]; - if next_profile.start - time_table[last_index - 1].end >= task.processing_time + if next_profile.start - time_table[last_index - 1].end + >= context.lower_bound(&task.processing_time) || !overflows_capacity_and_is_not_part_of_profile(context, task, next_profile, capacity) { break; @@ -545,7 +561,8 @@ fn find_index_last_profile_which_propagates_upper_bound< let mut first_index = profile_index - 1; loop { let previous_profile = time_table[first_index]; - if time_table[first_index + 1].start - previous_profile.end >= task.processing_time + if time_table[first_index + 1].start - previous_profile.end + >= context.lower_bound(&task.processing_time) || !overflows_capacity_and_is_not_part_of_profile( context, task, @@ -585,10 +602,11 @@ fn lower_bound_can_be_propagated_by_profile< capacity: i32, ) -> bool { pumpkin_assert_moderate!( - profile.height + task.resource_usage > capacity + profile.height + context.lower_bound(&task.resource_usage) > capacity && task_has_overlap_with_interval(context, task, profile.start, profile.end) , "It is checked whether a task can be propagated while the invariants do not hold - The task should overflow the capacity with the profile"); - (context.lower_bound(&task.start_variable) + task.processing_time) > profile.start + (context.lower_bound(&task.start_variable) + context.lower_bound(&task.processing_time)) + > profile.start && context.lower_bound(&task.start_variable) <= profile.end } @@ -609,9 +627,10 @@ fn upper_bound_can_be_propagated_by_profile< capacity: i32, ) -> bool { pumpkin_assert_moderate!( - profile.height + task.resource_usage > capacity + profile.height + context.lower_bound(&task.resource_usage) > capacity , "It is checked whether a task can be propagated while the invariants do not hold - The task should overflow the capacity with the profile"); - (context.upper_bound(&task.start_variable) + task.processing_time) > profile.start + (context.upper_bound(&task.start_variable) + context.lower_bound(&task.processing_time)) + > profile.start && context.upper_bound(&task.start_variable) <= profile.end } @@ -652,7 +671,7 @@ fn overflows_capacity_and_is_not_part_of_profile< profile: &ResourceProfile, capacity: i32, ) -> bool { - profile.height + task.resource_usage > capacity + profile.height + context.lower_bound(&task.resource_usage) > capacity && !has_mandatory_part_in_interval(context, task, profile.start, profile.end) } @@ -680,7 +699,12 @@ fn find_possible_updates< profile: &ResourceProfile, parameters: &CumulativeParameters, ) -> Vec { - if !can_be_updated_by_profile(context.as_readonly(), task, profile, parameters.capacity) { + if !can_be_updated_by_profile( + context.as_readonly(), + task, + profile, + context.upper_bound(¶meters.capacity), + ) { // If the task cannot be updated by the profile then we simply return the empty list vec![] } else { @@ -691,7 +715,7 @@ fn find_possible_updates< context.as_readonly(), task, profile, - parameters.capacity, + context.upper_bound(¶meters.capacity), ) { // The lower-bound of the task can be updated by the profile result.push(CanUpdate::LowerBound) @@ -700,7 +724,7 @@ fn find_possible_updates< context.as_readonly(), task, profile, - parameters.capacity, + context.upper_bound(¶meters.capacity), ) { // The upper-bound of the task can be updated by the profile result.push(CanUpdate::UpperBound) @@ -748,7 +772,8 @@ pub(crate) fn backtrack_update< // Stores whether the stored bounds did not include a mandatory part 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; + >= updatable_structures.get_stored_lower_bound(updated_task) + + context.lower_bound(&updated_task.processing_time); // If the stored bounds are already the same or the previous stored bounds did not include a // mandatory part (which means that this task will also not have mandatory part after diff --git a/pumpkin-crates/core/src/propagators/cumulative/utils/mod.rs b/pumpkin-crates/core/src/propagators/cumulative/utils/mod.rs index 411609aeb..65c2b1954 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/utils/mod.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/utils/mod.rs @@ -6,3 +6,4 @@ mod structs; pub(crate) use structs::*; pub(crate) mod util; +pub use structs::ArgTask; diff --git a/pumpkin-crates/core/src/propagators/cumulative/utils/structs/mandatory_part_adjustments.rs b/pumpkin-crates/core/src/propagators/cumulative/utils/structs/mandatory_part_adjustments.rs index cca90a9ab..c546f81a7 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/utils/structs/mandatory_part_adjustments.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/utils/structs/mandatory_part_adjustments.rs @@ -1,6 +1,10 @@ +use crate::engine::cp::propagation::contexts::propagation_context::ReadDomains; +use crate::variables::IntegerVariable; use std::cmp::Ordering; use std::ops::Range; +use crate::engine::propagation::PropagationContext; + use super::UpdatedTaskInfo; /// Represents adjustments to a mandatory part due to bound changes. @@ -76,16 +80,19 @@ impl MandatoryPartAdjustments { } } -impl UpdatedTaskInfo { +impl UpdatedTaskInfo { /// Returns the adjustments which need to be made to the time-table in the form of a /// [`MandatoryPartAdjustments`]. - pub(crate) fn get_mandatory_part_adjustments(&self) -> MandatoryPartAdjustments { + pub(crate) fn get_mandatory_part_adjustments( + &self, + context: PropagationContext, + ) -> MandatoryPartAdjustments { // We get the previous mandatory part - let previous_mandatory_part = - self.old_upper_bound..self.old_lower_bound + self.task.processing_time; + let previous_mandatory_part = self.old_upper_bound + ..self.old_lower_bound + context.lower_bound(&self.task.processing_time); // We also get the new mandatory part - let new_mandatory_part = - self.new_upper_bound..self.new_lower_bound + self.task.processing_time; + let new_mandatory_part = self.new_upper_bound + ..self.new_lower_bound + context.lower_bound(&self.task.processing_time); if previous_mandatory_part.is_empty() && new_mandatory_part.is_empty() { // If both are empty then no adjustments should be made diff --git a/pumpkin-crates/core/src/propagators/cumulative/utils/structs/mod.rs b/pumpkin-crates/core/src/propagators/cumulative/utils/structs/mod.rs index 03d40a593..81960874a 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/utils/structs/mod.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/utils/structs/mod.rs @@ -8,6 +8,7 @@ mod updated_task_info; pub(crate) use mandatory_part_adjustments::*; pub(crate) use parameters::*; pub(crate) use resource_profile::*; +pub use task::ArgTask; pub(crate) use task::*; pub(crate) use updatable_structures::*; pub(crate) use updated_task_info::*; diff --git a/pumpkin-crates/core/src/propagators/cumulative/utils/structs/parameters.rs b/pumpkin-crates/core/src/propagators/cumulative/utils/structs/parameters.rs index cc2e75cde..ce43427c8 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/utils/structs/parameters.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/utils/structs/parameters.rs @@ -1,6 +1,8 @@ +use crate::engine::cp::propagation::contexts::propagation_context::ReadDomains; use std::rc::Rc; use super::Task; +use crate::engine::propagation::PropagationContext; use crate::propagators::CumulativePropagatorOptions; use crate::variables::IntegerVariable; @@ -33,15 +35,17 @@ impl< > CumulativeParameters { pub(crate) fn new( + context: PropagationContext, tasks: Vec>, capacity: CVar, options: CumulativePropagatorOptions, - ) -> CumulativeParameters { + ) -> CumulativeParameters { let mut is_infeasible = false; let tasks = tasks .into_iter() .map(|task| { - is_infeasible |= task.resource_usage > capacity; + is_infeasible |= + context.lower_bound(&task.resource_usage) > context.upper_bound(&capacity); Rc::new(task) }) .collect::>() diff --git a/pumpkin-crates/core/src/propagators/cumulative/utils/structs/task.rs b/pumpkin-crates/core/src/propagators/cumulative/utils/structs/task.rs index 060df54c9..dfebe80cd 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/utils/structs/task.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/utils/structs/task.rs @@ -62,12 +62,12 @@ impl< /// The task which is passed as argument #[derive(Clone, Debug)] -pub(crate) struct ArgTask { +pub struct ArgTask { /// The [`IntegerVariable`] representing the start time of a task - pub(crate) start_time: Var, + pub start_time: Var, /// The processing time of the [`start_time`][ArgTask::start_time] (also referred to as /// duration of a task) - pub(crate) processing_time: PVar, + pub processing_time: PVar, /// How much of the resource the given task uses during its non-preemptive execution - pub(crate) resource_usage: RVar, + pub resource_usage: RVar, } diff --git a/pumpkin-crates/core/src/propagators/cumulative/utils/structs/updatable_structures.rs b/pumpkin-crates/core/src/propagators/cumulative/utils/structs/updatable_structures.rs index 67bd0fd9a..8d864a5ff 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/utils/structs/updatable_structures.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/utils/structs/updatable_structures.rs @@ -183,6 +183,13 @@ impl< parameters: &CumulativeParameters, ) { for task in parameters.tasks.iter() { + self.updates.push(UpdatedTaskInfo { + task: Rc::clone(task), + old_lower_bound: context.lower_bound(&task.start_variable), + old_upper_bound: context.upper_bound(&task.start_variable), + new_lower_bound: context.lower_bound(&task.start_variable), + new_upper_bound: context.upper_bound(&task.start_variable), + }); self.bounds.push(( context.lower_bound(&task.start_variable), context.upper_bound(&task.start_variable), diff --git a/pumpkin-crates/core/src/propagators/cumulative/utils/util.rs b/pumpkin-crates/core/src/propagators/cumulative/utils/util.rs index 60acc5435..aebcc0fb6 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/utils/util.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/utils/util.rs @@ -24,22 +24,23 @@ pub(crate) fn create_tasks< PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, >( + context: PropagationContext, arg_tasks: &[ArgTask], ) -> Vec> { // We order the tasks by non-decreasing resource usage, this allows certain optimizations let mut ordered_tasks = arg_tasks.to_vec(); - ordered_tasks.sort_by(|a, b| b.resource_usage.cmp(&a.resource_usage)); + ordered_tasks.sort_by_key(|a| context.lower_bound(&a.resource_usage)); let mut id = 0; ordered_tasks .iter() .filter_map(|x| { // We only add tasks which have a non-zero resource usage - if x.resource_usage > 0 { + if context.lower_bound(&x.resource_usage) > 0 { let return_value = Some(Task { start_variable: x.start_time.clone(), - processing_time: x.processing_time, - resource_usage: x.resource_usage, + processing_time: x.processing_time.clone(), + resource_usage: x.resource_usage.clone(), id: LocalId::from(id), }); diff --git a/pumpkin-crates/core/src/propagators/mod.rs b/pumpkin-crates/core/src/propagators/mod.rs index 384fac78f..46c247a2a 100644 --- a/pumpkin-crates/core/src/propagators/mod.rs +++ b/pumpkin-crates/core/src/propagators/mod.rs @@ -8,6 +8,7 @@ pub(crate) mod element; pub(crate) mod nogoods; mod reified_propagator; pub(crate) use arithmetic::*; +pub use cumulative::ArgTask; pub use cumulative::CumulativeExplanationType; pub use cumulative::CumulativeOptions; pub use cumulative::CumulativePropagationMethod; diff --git a/pumpkin-solver-py/src/constraints/globals.rs b/pumpkin-solver-py/src/constraints/globals.rs index ab553579a..0052adfc1 100644 --- a/pumpkin-solver-py/src/constraints/globals.rs +++ b/pumpkin-solver-py/src/constraints/globals.rs @@ -1,8 +1,10 @@ +use pumpkin_solver::constraint_arguments::ArgTask; use pumpkin_solver::constraints::Constraint; use pumpkin_solver::constraints::{self}; use pyo3::pyclass; use pyo3::pymethods; +use crate::constraints::arguments::PythonConstraintArg; use crate::model::Tag; use crate::variables::*; @@ -53,6 +55,99 @@ macro_rules! python_constraint { }; } +#[pyclass] +#[derive(Clone)] +pub struct Cumulative { + start_times: Vec, + durations: Vec, + resource_usages: Vec, + resource_capacity: IntExpression, + constraint_tag: Tag, +} + +#[pymethods] +impl Cumulative { + #[new] + pub fn new( + start_times: Vec, + durations: Vec, + resource_usages: Vec, + resource_capacity: IntExpression, + constraint_tag: Tag, + ) -> Self { + Self { + constraint_tag, + start_times, + durations, + resource_usages, + resource_capacity, + } + } +} + +impl Cumulative { + pub fn post( + self, + solver: &mut pumpkin_solver::Solver, + variable_map: &VariableMap, + ) -> Result<(), pumpkin_solver::ConstraintOperationError> { + let tasks = self + .start_times + .into_iter() + .zip(self.durations.iter()) + .zip(self.resource_usages.iter()) + .map(|((start_time, processing_time), resource_usage)| { + let start_time = start_time.to_solver_constraint_argument(variable_map); + let processing_time = processing_time.to_solver_constraint_argument(variable_map); + let resource_usage = resource_usage.to_solver_constraint_argument(variable_map); + ArgTask { + start_time, + processing_time, + resource_usage, + } + }) + .collect::>(); + constraints::cumulative( + tasks, + self.resource_capacity + .to_solver_constraint_argument(variable_map), + self.constraint_tag.0, + ) + .post(solver) + } + + pub fn implied_by( + self, + solver: &mut pumpkin_solver::Solver, + reification_literal: pumpkin_solver::variables::Literal, + variable_map: &VariableMap, + ) -> Result<(), pumpkin_solver::ConstraintOperationError> { + let tasks = self + .start_times + .into_iter() + .zip(self.durations.iter()) + .zip(self.resource_usages.iter()) + .map(|((start_time, processing_time), resource_usage)| { + let start_time = start_time.to_solver_constraint_argument(variable_map); + let processing_time = processing_time.to_solver_constraint_argument(variable_map); + let resource_usage = resource_usage.to_solver_constraint_argument(variable_map); + ArgTask { + start_time, + processing_time, + resource_usage, + } + }) + .collect::>(); + constraints::cumulative( + tasks, + self.resource_capacity + .to_solver_constraint_argument(variable_map), + self.constraint_tag.0, + ) + .implied_by(solver, reification_literal) + } +} + python_constraint! { Absolute: absolute { signed: IntExpression, @@ -94,15 +189,6 @@ python_constraint! { } } -python_constraint! { - Cumulative: cumulative { - start_times: Vec, - durations: Vec, - resource_requirements: Vec, - resource_capacity: i32, - } -} - python_constraint! { Division: division { numerator: IntExpression, diff --git a/pumpkin-solver/src/bin/pumpkin-solver/flatzinc/compiler/post_constraints.rs b/pumpkin-solver/src/bin/pumpkin-solver/flatzinc/compiler/post_constraints.rs index 5536e5500..ff1dac834 100644 --- a/pumpkin-solver/src/bin/pumpkin-solver/flatzinc/compiler/post_constraints.rs +++ b/pumpkin-solver/src/bin/pumpkin-solver/flatzinc/compiler/post_constraints.rs @@ -2,6 +2,7 @@ use std::rc::Rc; +use pumpkin_core::constraint_arguments::ArgTask; use pumpkin_solver::constraints; use pumpkin_solver::constraints::Constraint; use pumpkin_solver::constraints::NegatableConstraint; @@ -271,9 +272,18 @@ fn compile_cumulative( let resource_capacity = context.resolve_integer_constant_from_expr(&exprs[3])?; let post_result = constraints::cumulative_with_options( - start_times.iter().copied(), - durations.iter().copied(), - resource_requirements.iter().copied(), + start_times + .iter() + .zip(durations.iter()) + .zip(resource_requirements.iter()) + .map( + |((&start_time, &processing_time), &resource_usage)| ArgTask { + start_time, + processing_time, + resource_usage, + }, + ) + .collect::>(), resource_capacity, options.cumulative_options, constraint_tag, diff --git a/pumpkin-solver/src/bin/pumpkin-solver/flatzinc/mod.rs b/pumpkin-solver/src/bin/pumpkin-solver/flatzinc/mod.rs index 37cf19838..b8742bebd 100644 --- a/pumpkin-solver/src/bin/pumpkin-solver/flatzinc/mod.rs +++ b/pumpkin-solver/src/bin/pumpkin-solver/flatzinc/mod.rs @@ -16,13 +16,13 @@ use pumpkin_core::branching::branchers::alternating::AlternatingBrancher; use pumpkin_core::statistics::log_statistic; use pumpkin_solver::branching::branchers::dynamic_brancher::DynamicBrancher; use pumpkin_solver::branching::Brancher; +use pumpkin_solver::constraint_arguments::CumulativeOptions; #[cfg(doc)] use pumpkin_solver::constraints::cumulative; use pumpkin_solver::optimisation::linear_sat_unsat::LinearSatUnsat; use pumpkin_solver::optimisation::linear_unsat_sat::LinearUnsatSat; use pumpkin_solver::optimisation::OptimisationDirection; use pumpkin_solver::optimisation::OptimisationStrategy; -use pumpkin_solver::options::CumulativeOptions; use pumpkin_solver::results::solution_iterator::IteratedSolution; use pumpkin_solver::results::OptimisationResult; use pumpkin_solver::results::ProblemSolution; diff --git a/pumpkin-solver/src/bin/pumpkin-solver/main.rs b/pumpkin-solver/src/bin/pumpkin-solver/main.rs index 3ea95c5b9..dea6af55b 100644 --- a/pumpkin-solver/src/bin/pumpkin-solver/main.rs +++ b/pumpkin-solver/src/bin/pumpkin-solver/main.rs @@ -24,6 +24,9 @@ use maxsat::PseudoBooleanEncoding; use parsers::dimacs::parse_cnf; use parsers::dimacs::SolverArgs; use parsers::dimacs::SolverDimacsSink; +use pumpkin_core::constraint_arguments::CumulativeExplanationType; +use pumpkin_core::constraint_arguments::CumulativeOptions; +use pumpkin_core::constraint_arguments::CumulativePropagationMethod; use pumpkin_solver::convert_case::Case; use pumpkin_solver::optimisation::OptimisationStrategy; use pumpkin_solver::options::*; From 42807ed2d229eb3b91b20efb28b2103ab206856f Mon Sep 17 00:00:00 2001 From: Imko Marijnissen Date: Tue, 16 Sep 2025 11:21:41 +0900 Subject: [PATCH 03/17] fix: incremental backtracking --- .../core/src/engine/variables/constant.rs | 15 +++++++-------- .../checks.rs | 2 +- .../insertion.rs | 2 +- .../removal.rs | 2 +- .../time_table_over_interval_incremental.rs | 6 +++++- .../time_table_per_point_incremental.rs | 8 ++++++-- .../utils/structs/mandatory_part_adjustments.rs | 7 +++---- .../cumulative/utils/structs/parameters.rs | 2 +- 8 files changed, 25 insertions(+), 19 deletions(-) diff --git a/pumpkin-crates/core/src/engine/variables/constant.rs b/pumpkin-crates/core/src/engine/variables/constant.rs index 725ea92cf..823e73ab1 100644 --- a/pumpkin-crates/core/src/engine/variables/constant.rs +++ b/pumpkin-crates/core/src/engine/variables/constant.rs @@ -1,11 +1,10 @@ -use crate::{ - engine::{ - notifications::{DomainEvent, OpaqueDomainEvent}, - Assignments, - }, - predicates::{Predicate, PredicateConstructor}, - variables::{IntegerVariable, TransformableVariable}, -}; +use crate::engine::notifications::DomainEvent; +use crate::engine::notifications::OpaqueDomainEvent; +use crate::engine::Assignments; +use crate::predicates::Predicate; +use crate::predicates::PredicateConstructor; +use crate::variables::IntegerVariable; +use crate::variables::TransformableVariable; impl IntegerVariable for i32 { type AffineView = i32; diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/checks.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/checks.rs index 0e719340f..739d4bbc4 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/checks.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/checks.rs @@ -1,11 +1,11 @@ //! Contains the checks which are done when a new mandatory part is added in the propagate method to //! determine which profiles should be added and how existing profiles should be adjusted. -use crate::engine::cp::propagation::contexts::propagation_context::ReadDomains; use std::cmp::max; use std::cmp::min; use std::ops::Range; use std::rc::Rc; +use crate::engine::cp::propagation::contexts::propagation_context::ReadDomains; use crate::engine::propagation::PropagationContext; use crate::propagators::OverIntervalTimeTableType; use crate::propagators::ResourceProfile; diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/insertion.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/insertion.rs index 5ee988847..42736f754 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/insertion.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/insertion.rs @@ -1,9 +1,9 @@ //! Contains the functions necessary for inserting the appropriate profiles into the time-table //! based on the added mandatory part. -use crate::engine::cp::propagation::contexts::propagation_context::ReadDomains; use std::ops::Range; use std::rc::Rc; +use crate::engine::cp::propagation::contexts::propagation_context::ReadDomains; use crate::engine::propagation::PropagationContext; use crate::propagators::cumulative::time_table::over_interval_incremental_propagator::checks; use crate::propagators::OverIntervalTimeTableType; diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/removal.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/removal.rs index 4fda999cd..64802b45d 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/removal.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/removal.rs @@ -1,11 +1,11 @@ //! Contains the functions necessary for removing the appropriate profiles into the time-table //! based on the reduced mandatory part. -use crate::engine::cp::propagation::contexts::propagation_context::ReadDomains; use std::cmp::max; use std::cmp::min; use std::ops::Range; use std::rc::Rc; +use crate::engine::cp::propagation::contexts::propagation_context::ReadDomains; use crate::engine::propagation::PropagationContext; use crate::propagators::OverIntervalTimeTableType; use crate::propagators::ResourceProfile; diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/time_table_over_interval_incremental.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/time_table_over_interval_incremental.rs index fd00b2e42..3278dd3f9 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/time_table_over_interval_incremental.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/time_table_over_interval_incremental.rs @@ -181,7 +181,11 @@ impl< let parameters = CumulativeParameters::new(context.as_readonly(), tasks, capacity, cumulative_options); - register_tasks(¶meters.tasks, context.reborrow(), false); + register_tasks( + ¶meters.tasks, + context.reborrow(), + cumulative_options.incremental_backtracking, + ); let mut updatable_structures = UpdatableStructures::new(¶meters); updatable_structures.initialise_bounds_and_remove_fixed(context.as_readonly(), ¶meters); diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs index 9925529c1..1b0421344 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs @@ -1,4 +1,3 @@ -use crate::engine::cp::propagation::contexts::propagation_context::ReadDomains; use std::collections::btree_map::Entry; use std::collections::BTreeMap; use std::fmt::Debug; @@ -8,6 +7,7 @@ use crate::basic_types::Inconsistency; use crate::basic_types::PropagationStatusCP; use crate::basic_types::PropagatorConflict; use crate::conjunction; +use crate::engine::cp::propagation::contexts::propagation_context::ReadDomains; use crate::engine::notifications::DomainEvent; use crate::engine::notifications::OpaqueDomainEvent; use crate::engine::propagation::constructor::PropagatorConstructor; @@ -177,7 +177,11 @@ impl< let parameters = CumulativeParameters::new(context.as_readonly(), tasks, capacity, cumulative_options); - register_tasks(¶meters.tasks, context.reborrow(), false); + register_tasks( + ¶meters.tasks, + context.reborrow(), + cumulative_options.incremental_backtracking, + ); let mut updatable_structures = UpdatableStructures::new(¶meters); updatable_structures.initialise_bounds_and_remove_fixed(context.as_readonly(), ¶meters); diff --git a/pumpkin-crates/core/src/propagators/cumulative/utils/structs/mandatory_part_adjustments.rs b/pumpkin-crates/core/src/propagators/cumulative/utils/structs/mandatory_part_adjustments.rs index c546f81a7..65e18f315 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/utils/structs/mandatory_part_adjustments.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/utils/structs/mandatory_part_adjustments.rs @@ -1,11 +1,10 @@ -use crate::engine::cp::propagation::contexts::propagation_context::ReadDomains; -use crate::variables::IntegerVariable; use std::cmp::Ordering; use std::ops::Range; -use crate::engine::propagation::PropagationContext; - use super::UpdatedTaskInfo; +use crate::engine::cp::propagation::contexts::propagation_context::ReadDomains; +use crate::engine::propagation::PropagationContext; +use crate::variables::IntegerVariable; /// Represents adjustments to a mandatory part due to bound changes. /// diff --git a/pumpkin-crates/core/src/propagators/cumulative/utils/structs/parameters.rs b/pumpkin-crates/core/src/propagators/cumulative/utils/structs/parameters.rs index ce43427c8..46f176d48 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/utils/structs/parameters.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/utils/structs/parameters.rs @@ -1,7 +1,7 @@ -use crate::engine::cp::propagation::contexts::propagation_context::ReadDomains; use std::rc::Rc; use super::Task; +use crate::engine::cp::propagation::contexts::propagation_context::ReadDomains; use crate::engine::propagation::PropagationContext; use crate::propagators::CumulativePropagatorOptions; use crate::variables::IntegerVariable; From e351c9b79cfca193ebc4d55627d0c9c5f555b42e Mon Sep 17 00:00:00 2001 From: Imko Marijnissen Date: Tue, 16 Sep 2025 11:57:49 +0900 Subject: [PATCH 04/17] feat: lower-bound propagation of capacity --- minizinc/lib/fzn_cumulative.mzn | 88 +- .../debug.rs | 6 +- .../synchronisation.rs | 9 +- .../time_table_over_interval_incremental.rs | 15 +- .../synchronisation.rs | 9 +- .../time_table_per_point_incremental.rs | 19 +- .../time_table/propagation_handler.rs | 6 +- .../time_table/time_table_over_interval.rs | 60 +- .../time_table/time_table_per_point.rs | 48 +- .../src/propagators/cumulative/utils/util.rs | 19 + pumpkin-macros/src/lib.rs | 30 + .../flatzinc/compiler/post_constraints.rs | 232 +++- .../mzn_constraints/cumulative_var.expected | 1101 +++++++++++++++++ .../tests/mzn_constraints/cumulative_var.fzn | 17 + .../mzn_constraints/cumulative_var.template | 23 + 15 files changed, 1477 insertions(+), 205 deletions(-) create mode 100644 pumpkin-solver/tests/mzn_constraints/cumulative_var.expected create mode 100644 pumpkin-solver/tests/mzn_constraints/cumulative_var.fzn create mode 100644 pumpkin-solver/tests/mzn_constraints/cumulative_var.template diff --git a/minizinc/lib/fzn_cumulative.mzn b/minizinc/lib/fzn_cumulative.mzn index f51922d27..896015dbf 100644 --- a/minizinc/lib/fzn_cumulative.mzn +++ b/minizinc/lib/fzn_cumulative.mzn @@ -13,93 +13,9 @@ predicate fzn_cumulative(array[int] of var int: s, /\ if is_fixed(d) /\ is_fixed(r) /\ is_fixed(b) then pumpkin_cumulative(s, fix(d), fix(r), fix(b)) else - fzn_decomposition_cumulative(s, d, r, b) + pumpkin_cumulative_vars(s, d, r, b) endif ); -% The following predicate is taken from https://github.com/MiniZinc/libminizinc/blob/2.5.5/share/minizinc/std/cumulative.mzn -predicate fzn_decomposition_cumulative(array[int] of var int: s, - array[int] of var int: d, - array[int] of var int: r, - var int: b) = - assert(index_set(s) == index_set(d) /\ index_set(s) == index_set(r), - "cumulative: the 3 array arguments must have identical index sets", - if length(s) >= 1 then - assert(lb_array(d) >= 0 /\ lb_array(r) >= 0, - "cumulative: durations and resource usages must be non-negative", - if - %% the previous test will not work for resources usages like [2,3,3,4,4] with a bound of 4 since the 2 appears exactly once - let { int: mr = lb_array(r); - int: mri = arg_min([ lb(r[i]) | i in index_set(r) ]) } in - forall(i in index_set(r)) - (is_fixed(r[i]) /\ (fix(r[i]) + mr > ub(b) \/ i = mri)) - then - if forall(i in index_set(d))(is_fixed(d[i]) /\ fix(d[i]) == 1) - then - all_different(s) - else - disjunctive(s, d) - endif - else - decomposition_cumulative(s, d, r, b) - endif - ) - endif); - -% The following predicate is taken from https://github.com/MiniZinc/libminizinc/blob/2.5.5/share/minizinc/std/fzn_cumulative.mzn -predicate decomposition_cumulative(array[int] of var int: s, - array[int] of var int: d, - array[int] of var int: r, var int: b) = - let { - set of int: Tasks = - {i | i in index_set(s) where ub(r[i]) > 0 /\ ub(d[i]) > 0 } - } in - if 0==card(Tasks) then /*true*/ 0==card(index_set(s)) \/ b>=0 - else - let { - int: early = min([ lb(s[i]) | i in Tasks ]), - int: late = max([ ub(s[i]) + ub(d[i]) | i in Tasks ]) - } in ( - if late - early > 5000 then - fzn_cumulative_task(s, d, r, b) - else - fzn_cumulative_time(s, d, r, b) - endif - ) - endif - ; - -% The following predicate is taken from https://github.com/MiniZinc/libminizinc/blob/2.5.5/share/minizinc/std/fzn_cumulative.mzn -predicate fzn_cumulative_time(array[int] of var int: s, - array[int] of var int: d, - array[int] of var int: r, var int: b) = - let { - set of int: Tasks = - {i | i in index_set(s) where ub(r[i]) > 0 /\ ub(d[i]) > 0 }, - int: early = min([ lb(s[i]) | i in Tasks ]), - int: late = max([ ub(s[i]) + ub(d[i]) | i in Tasks ]) - } in ( - forall( t in early..late ) ( - b >= sum( i in Tasks ) ( - bool2int(s[i] <= t /\ t < s[i] + d[i]) * r[i] - ) - ) - ); - -% The following predicate is taken from https://github.com/MiniZinc/libminizinc/blob/2.5.5/share/minizinc/std/fzn_cumulative.mzn -predicate fzn_cumulative_task(array[int] of var int: s, - array[int] of var int: d, - array[int] of var int: r, var int: b) = - let { - set of int: Tasks = - {i | i in index_set(s) where ub(r[i]) > 0 /\ ub(d[i]) > 0 } - } in ( - forall( j in Tasks ) ( - b >= r[j] + sum( i in Tasks where i != j ) ( - bool2int(s[i] <= s[j] /\ s[j] < s[i] + d[i] ) * r[i] - ) - ) - ); - predicate pumpkin_cumulative(array[int] of var int: s, array[int] of int: d, array[int] of int: r, int: b); -predicate pumpkin_cumulative_vars(array[int] of var int: s, array[int] of var int: d, array[int] of var int: r, var int: b); \ No newline at end of file +predicate pumpkin_cumulative_vars(array[int] of var int: s, array[int] of var int: d, array[int] of var int: r, var int: b); diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/debug.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/debug.rs index 4e3bc8ddb..25dbfa094 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/debug.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/debug.rs @@ -2,7 +2,7 @@ use std::ops::Range; use crate::containers::HashSet; -use crate::engine::propagation::PropagationContext; +use crate::engine::propagation::PropagationContextMut; use crate::proof::InferenceCode; use crate::propagators::create_time_table_over_interval_from_scratch; use crate::propagators::CumulativeParameters; @@ -12,7 +12,7 @@ use crate::pumpkin_assert_extreme; use crate::pumpkin_assert_simple; use crate::variables::IntegerVariable; -/// Determines whether the provided `time_table` is the same as the one creatd from scratch +/// Determines whether the provided `time_table` is the same as the one created from scratch /// using the following checks: /// - The time-tables should contain the same number of profiles /// - For each profile it should hold that @@ -28,7 +28,7 @@ pub(crate) fn time_tables_are_the_same_interval< CVar: IntegerVariable + 'static, const SYNCHRONISE: bool, >( - context: PropagationContext, + context: &mut PropagationContextMut, inference_code: InferenceCode, time_table: &OverIntervalTimeTableType, parameters: &CumulativeParameters, diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/synchronisation.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/synchronisation.rs index 364199f9a..c5bbbb4a9 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/synchronisation.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/synchronisation.rs @@ -5,10 +5,11 @@ use super::debug::merge_profiles; use crate::basic_types::Inconsistency; use crate::basic_types::PropagationStatusCP; use crate::engine::propagation::PropagationContext; +use crate::engine::propagation::PropagationContextMut; use crate::engine::propagation::ReadDomains; use crate::proof::InferenceCode; use crate::propagators::create_time_table_over_interval_from_scratch; -use crate::propagators::cumulative::time_table::propagation_handler::create_conflict_explanation; +use crate::propagators::cumulative::time_table::propagation_handler::create_explanation_profile_height; use crate::propagators::CumulativeParameters; use crate::propagators::OverIntervalTimeTableType; use crate::propagators::ResourceProfile; @@ -66,13 +67,13 @@ pub(crate) fn check_synchronisation_conflict_explanation_over_interval< CVar: IntegerVariable + 'static, >( synchronised_conflict_explanation: &PropagationStatusCP, - context: PropagationContext, + context: &mut PropagationContextMut, parameters: &CumulativeParameters, inference_code: InferenceCode, ) -> bool { let error_from_scratch = create_time_table_over_interval_from_scratch(context, parameters, inference_code); - if let Err(explanation_scratch) = error_from_scratch { + if let Err(Inconsistency::Conflict(explanation_scratch)) = error_from_scratch { if let Err(Inconsistency::Conflict(explanation)) = &synchronised_conflict_explanation { // We check whether both inconsistencies are of the same type and then we check their // corresponding explanations @@ -121,7 +122,7 @@ pub(crate) fn create_synchronised_conflict_explanation< index += 1; } - Err(create_conflict_explanation( + Err(create_explanation_profile_height( context, inference_code, &ResourceProfile { diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/time_table_over_interval_incremental.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/time_table_over_interval_incremental.rs index 3278dd3f9..797c1931b 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/time_table_over_interval_incremental.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/time_table_over_interval_incremental.rs @@ -28,7 +28,7 @@ use crate::propagators::cumulative::time_table::over_interval_incremental_propag use crate::propagators::cumulative::time_table::over_interval_incremental_propagator::synchronisation::create_synchronised_conflict_explanation; use crate::propagators::cumulative::time_table::over_interval_incremental_propagator::synchronisation::find_synchronised_conflict; use crate::propagators::cumulative::time_table::over_interval_incremental_propagator::synchronisation::synchronise_time_table; -use crate::propagators::cumulative::time_table::propagation_handler::create_conflict_explanation; +use crate::propagators::cumulative::time_table::propagation_handler::create_explanation_profile_height; use crate::propagators::cumulative::time_table::time_table_util::backtrack_update; use crate::propagators::cumulative::time_table::time_table_util::has_overlap_with_interval; use crate::propagators::cumulative::time_table::time_table_util::insert_update; @@ -183,6 +183,7 @@ impl< CumulativeParameters::new(context.as_readonly(), tasks, capacity, cumulative_options); register_tasks( ¶meters.tasks, + ¶meters.capacity, context.reborrow(), cumulative_options.incremental_backtracking, ); @@ -227,7 +228,7 @@ impl< ); if let Err(conflict_tasks) = result { if conflict.is_none() { - conflict = Some(Err(create_conflict_explanation( + conflict = Some(Err(create_explanation_profile_height( context, self.inference_code, &conflict_tasks, @@ -292,7 +293,7 @@ impl< if self.is_time_table_outdated { // We create the time-table from scratch (and return an error if it overflows) self.time_table = create_time_table_over_interval_from_scratch( - context.as_readonly(), + context, &self.parameters, self.inference_code, )?; @@ -370,7 +371,7 @@ impl< pumpkin_assert_extreme!( check_synchronisation_conflict_explanation_over_interval( &synchronised_conflict_explanation, - context.as_readonly(), + context, &self.parameters, self.inference_code, ), @@ -391,7 +392,7 @@ impl< if let Some(conflicting_profile) = conflicting_profile { pumpkin_assert_extreme!( create_time_table_over_interval_from_scratch( - context.as_readonly(), + context, &self.parameters, self.inference_code, ) @@ -401,7 +402,7 @@ impl< // We have found the previous conflict self.found_previous_conflict = true; - return Err(create_conflict_explanation( + return Err(create_explanation_profile_height( context.as_readonly(), self.inference_code, conflicting_profile, @@ -461,7 +462,7 @@ impl< pumpkin_assert_extreme!( debug::time_tables_are_the_same_interval::( - context.as_readonly(), + &mut context, self.inference_code, &self.time_table, &self.parameters, diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/synchronisation.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/synchronisation.rs index 5338ce915..a5bc5bc2e 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/synchronisation.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/synchronisation.rs @@ -4,9 +4,10 @@ use crate::basic_types::Inconsistency; use crate::basic_types::PropagationStatusCP; use crate::engine::cp::propagation::contexts::propagation_context::ReadDomains; use crate::engine::propagation::PropagationContext; +use crate::engine::propagation::PropagationContextMut; use crate::proof::InferenceCode; use crate::propagators::create_time_table_per_point_from_scratch; -use crate::propagators::cumulative::time_table::propagation_handler::create_conflict_explanation; +use crate::propagators::cumulative::time_table::propagation_handler::create_explanation_profile_height; use crate::propagators::CumulativeParameters; use crate::propagators::PerPointTimeTableType; use crate::propagators::ResourceProfile; @@ -24,13 +25,13 @@ pub(crate) fn check_synchronisation_conflict_explanation_per_point< CVar: IntegerVariable + 'static, >( synchronised_conflict_explanation: &PropagationStatusCP, - context: PropagationContext, + context: &mut PropagationContextMut, inference_code: InferenceCode, parameters: &CumulativeParameters, ) -> bool { let error_from_scratch = create_time_table_per_point_from_scratch(context, inference_code, parameters); - if let Err(explanation_scratch) = error_from_scratch { + if let Err(Inconsistency::Conflict(explanation_scratch)) = error_from_scratch { if let Err(Inconsistency::Conflict(conflict)) = &synchronised_conflict_explanation { // We check whether both inconsistencies are of the same type and then we check their // corresponding explanations @@ -157,7 +158,7 @@ pub(crate) fn create_synchronised_conflict_explanation< ) .collect(); - Err(create_conflict_explanation( + Err(create_explanation_profile_height( context, inference_code, &ResourceProfile { diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs index 1b0421344..b441dffe6 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs @@ -26,7 +26,7 @@ use crate::propagators::cumulative::time_table::per_point_incremental_propagator use crate::propagators::cumulative::time_table::per_point_incremental_propagator::synchronisation::create_synchronised_conflict_explanation; use crate::propagators::cumulative::time_table::per_point_incremental_propagator::synchronisation::find_synchronised_conflict; use crate::propagators::cumulative::time_table::per_point_incremental_propagator::synchronisation::synchronise_time_table; -use crate::propagators::cumulative::time_table::propagation_handler::create_conflict_explanation; +use crate::propagators::cumulative::time_table::propagation_handler::create_explanation_profile_height; use crate::propagators::cumulative::time_table::time_table_util::backtrack_update; use crate::propagators::cumulative::time_table::time_table_util::insert_update; use crate::propagators::cumulative::time_table::time_table_util::propagate_based_on_timetable; @@ -179,6 +179,7 @@ impl< CumulativeParameters::new(context.as_readonly(), tasks, capacity, cumulative_options); register_tasks( ¶meters.tasks, + ¶meters.capacity, context.reborrow(), cumulative_options.incremental_backtracking, ); @@ -228,7 +229,7 @@ impl< && conflict.is_none() { // The newly introduced mandatory part(s) caused an overflow of the resource - conflict = Some(Err(create_conflict_explanation( + conflict = Some(Err(create_explanation_profile_height( context, self.inference_code, current_profile, @@ -297,7 +298,7 @@ impl< if self.is_time_table_outdated { // We create the time-table from scratch (and return an error if it overflows) self.time_table = create_time_table_per_point_from_scratch( - context.as_readonly(), + context, self.inference_code, &self.parameters, )?; @@ -382,7 +383,7 @@ impl< pumpkin_assert_extreme!( check_synchronisation_conflict_explanation_per_point( &synchronised_conflict_explanation, - context.as_readonly(), + context, self.inference_code, &self.parameters, ), @@ -407,7 +408,7 @@ impl< if let Some(conflicting_profile) = conflicting_profile { pumpkin_assert_extreme!( create_time_table_per_point_from_scratch( - context.as_readonly(), + context, self.inference_code, &self.parameters ) @@ -417,7 +418,7 @@ impl< // We have found the previous conflict self.found_previous_conflict = true; - return Err(create_conflict_explanation( + return Err(create_explanation_profile_height( context.as_readonly(), self.inference_code, conflicting_profile, @@ -482,7 +483,7 @@ impl< CVar, SYNCHRONISE, >( - context.as_readonly(), + &mut context, self.inference_code, &self.time_table, &self.parameters @@ -610,7 +611,7 @@ impl< /// Contains functions related to debugging mod debug { - use crate::engine::propagation::PropagationContext; + use crate::engine::propagation::PropagationContextMut; use crate::proof::InferenceCode; use crate::propagators::create_time_table_per_point_from_scratch; use crate::propagators::CumulativeParameters; @@ -633,7 +634,7 @@ mod debug { CVar: IntegerVariable + 'static, const SYNCHRONISE: bool, >( - context: PropagationContext, + context: &mut PropagationContextMut, inference_code: InferenceCode, time_table: &PerPointTimeTableType, parameters: &CumulativeParameters, diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs index 64e8d6df3..4f26be826 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs @@ -441,7 +441,7 @@ impl CumulativePropagationHandler { /// Creates an explanation of the conflict caused by `conflict_profile` based on the provided /// `explanation_type`. -pub(crate) fn create_conflict_explanation( +pub(crate) fn create_explanation_profile_height( context: PropagationContext, inference_code: InferenceCode, conflict_profile: &ResourceProfile, @@ -474,7 +474,7 @@ where pub(crate) mod test_propagation_handler { use std::rc::Rc; - use super::create_conflict_explanation; + use super::create_explanation_profile_height; use super::CumulativeExplanationType; use super::CumulativePropagationHandler; use crate::containers::StorageKey; @@ -543,7 +543,7 @@ pub(crate) mod test_propagation_handler { height: 1, }; - let reason = create_conflict_explanation( + let reason = create_explanation_profile_height( PropagationContext::new(&self.assignments), self.propagation_handler.inference_code, &profile, diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_over_interval.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_over_interval.rs index 737bba704..d0b9775c1 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_over_interval.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_over_interval.rs @@ -19,9 +19,10 @@ use crate::engine::propagation::PropagationContextMut; use crate::engine::propagation::Propagator; use crate::engine::propagation::ReadDomains; use crate::engine::variables::IntegerVariable; +use crate::predicate; use crate::proof::ConstraintTag; use crate::proof::InferenceCode; -use crate::propagators::cumulative::time_table::propagation_handler::create_conflict_explanation; +use crate::propagators::cumulative::time_table::propagation_handler::create_explanation_profile_height; use crate::propagators::util::create_tasks; use crate::propagators::util::register_tasks; use crate::propagators::util::update_bounds_task; @@ -140,7 +141,12 @@ impl< let parameters = CumulativeParameters::new(context.as_readonly(), tasks, capacity, cumulative_options); - register_tasks(¶meters.tasks, context.reborrow(), false); + register_tasks( + ¶meters.tasks, + ¶meters.capacity, + context.reborrow(), + false, + ); let mut updatable_structures = UpdatableStructures::new(¶meters); updatable_structures.initialise_bounds_and_remove_fixed(context.as_readonly(), ¶meters); @@ -170,7 +176,7 @@ impl< } let time_table = create_time_table_over_interval_from_scratch( - context.as_readonly(), + &mut context, &self.parameters, self.inference_code, )?; @@ -197,6 +203,11 @@ impl< local_id: LocalId, event: OpaqueDomainEvent, ) -> EnqueueDecision { + if local_id.unpack() as usize >= self.parameters.tasks.len() { + // The upper-bound of the capacity has been updated; we should enqueue + return EnqueueDecision::Enqueue; + } + let updated_task = Rc::clone(&self.parameters.tasks[local_id.unpack() as usize]); // Note that it could be the case that `is_time_table_empty` is inaccurate here since it // wasn't updated in `synchronise`; however, `synchronise` will only remove profiles @@ -264,12 +275,12 @@ pub(crate) fn create_time_table_over_interval_from_scratch< RVar: IntegerVariable + 'static, CVar: IntegerVariable + 'static, >( - context: PropagationContext, + context: &mut PropagationContextMut, parameters: &CumulativeParameters, inference_code: InferenceCode, -) -> Result, PropagatorConflict> { +) -> Result, Inconsistency> { // First we create a list of all the events (i.e. start and ends of mandatory parts) - let events = create_events(context, parameters); + let events = create_events(context.as_readonly(), parameters); // Then we create a time-table using these events create_time_table_from_events(events, context, inference_code, parameters) @@ -355,10 +366,10 @@ fn create_time_table_from_events< CVar: IntegerVariable + 'static, >( events: Vec>, - context: PropagationContext, + context: &mut PropagationContextMut, inference_code: InferenceCode, parameters: &CumulativeParameters, -) -> Result, PropagatorConflict> { +) -> Result, Inconsistency> { pumpkin_assert_extreme!( events.is_empty() || (0..events.len() - 1) @@ -421,20 +432,22 @@ fn create_time_table_from_events< profile_tasks: current_profile_tasks.clone(), height: current_resource_usage, }; - if is_conflicting { - // We have found a conflict and the profile has been ended, we can report the - // conflict using the current profile - return Err(create_conflict_explanation( - context, + if new_profile.height > context.lower_bound(¶meters.capacity) { + context.post( + predicate!(parameters.capacity >= new_profile.height), + create_explanation_profile_height( + context.as_readonly(), + inference_code, + &new_profile, + parameters.options.explanation_type, + ) + .conjunction, inference_code, - &new_profile, - parameters.options.explanation_type, - )); - } else { - // We end the current profile, creating a profile from [start_of_interval, - // time_stamp) - time_table.push(new_profile); + )?; } + // We end the current profile, creating a profile from [start_of_interval, + // time_stamp) + time_table.push(new_profile); } // Process the current event, note that `change_in_resource_usage` can be negative pumpkin_assert_simple!( @@ -521,11 +534,8 @@ pub(crate) fn debug_propagate_from_scratch_time_table_interval< ) -> PropagationStatusCP { // We first create a time-table over interval and return an error if there was // an overflow of the resource capacity while building the time-table - let time_table = create_time_table_over_interval_from_scratch( - context.as_readonly(), - parameters, - inference_code, - )?; + let time_table = + create_time_table_over_interval_from_scratch(context, parameters, inference_code)?; // Then we check whether propagation can take place propagate_based_on_timetable( context, diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_per_point.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_per_point.rs index d4ac5bf32..4071e928a 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_per_point.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_per_point.rs @@ -24,9 +24,10 @@ use crate::engine::propagation::PropagationContext; use crate::engine::propagation::PropagationContextMut; use crate::engine::propagation::Propagator; use crate::engine::variables::IntegerVariable; +use crate::predicate; use crate::proof::ConstraintTag; use crate::proof::InferenceCode; -use crate::propagators::cumulative::time_table::propagation_handler::create_conflict_explanation; +use crate::propagators::cumulative::time_table::propagation_handler::create_explanation_profile_height; use crate::propagators::util::create_tasks; use crate::propagators::util::register_tasks; use crate::propagators::util::update_bounds_task; @@ -134,7 +135,12 @@ impl< let parameters = CumulativeParameters::new(context.as_readonly(), tasks, capacity, cumulative_options); - register_tasks(¶meters.tasks, context.reborrow(), false); + register_tasks( + ¶meters.tasks, + ¶meters.capacity, + context.reborrow(), + false, + ); let mut updatable_structures = UpdatableStructures::new(¶meters); updatable_structures.initialise_bounds_and_remove_fixed(context.as_readonly(), ¶meters); @@ -164,7 +170,7 @@ impl< } let time_table = create_time_table_per_point_from_scratch( - context.as_readonly(), + &mut context, self.inference_code, &self.parameters, )?; @@ -191,6 +197,11 @@ impl< local_id: LocalId, event: OpaqueDomainEvent, ) -> EnqueueDecision { + if local_id.unpack() as usize >= self.parameters.tasks.len() { + // The upper-bound of the capacity has been updated; we should enqueue + return EnqueueDecision::Enqueue; + } + let updated_task = Rc::clone(&self.parameters.tasks[local_id.unpack() as usize]); // Note that it could be the case that `is_time_table_empty` is inaccurate here since it // wasn't updated in `synchronise`; however, `synchronise` will only remove profiles @@ -205,7 +216,7 @@ impl< self.is_time_table_empty, ); - // Note that the non-incremental proapgator does not make use of `result.updated` since it + // Note that the non-incremental propagator does not make use of `result.updated` since it // propagates from scratch anyways update_bounds_task( context.as_readonly(), @@ -258,10 +269,10 @@ pub(crate) fn create_time_table_per_point_from_scratch< RVar: IntegerVariable + 'static, CVar: IntegerVariable + 'static, >( - context: PropagationContext, + context: &mut PropagationContextMut, inference_code: InferenceCode, parameters: &CumulativeParameters, -) -> Result, PropagatorConflict> { +) -> Result, Inconsistency> { let mut time_table: PerPointTimeTableType = PerPointTimeTableType::new(); // First we go over all tasks and determine their mandatory parts for task in parameters.tasks.iter() { @@ -280,15 +291,18 @@ pub(crate) fn create_time_table_per_point_from_scratch< current_profile.height += context.lower_bound(&task.resource_usage); current_profile.profile_tasks.push(Rc::clone(task)); - if current_profile.height > context.upper_bound(¶meters.capacity) { - // The addition of the current task to the resource profile has caused an - // overflow - return Err(create_conflict_explanation( - context, + if current_profile.height > context.lower_bound(¶meters.capacity) { + context.post( + predicate!(parameters.capacity >= current_profile.height), + create_explanation_profile_height( + context.as_readonly(), + inference_code, + current_profile, + parameters.options.explanation_type, + ) + .conjunction, inference_code, - current_profile, - parameters.options.explanation_type, - )); + )?; } } } @@ -315,11 +329,7 @@ pub(crate) fn debug_propagate_from_scratch_time_table_point< ) -> PropagationStatusCP { // We first create a time-table per point and return an error if there was // an overflow of the resource capacity while building the time-table - let time_table = create_time_table_per_point_from_scratch( - context.as_readonly(), - inference_code, - parameters, - )?; + let time_table = create_time_table_per_point_from_scratch(context, inference_code, parameters)?; // Then we check whether propagation can take place propagate_based_on_timetable( context, diff --git a/pumpkin-crates/core/src/propagators/cumulative/utils/util.rs b/pumpkin-crates/core/src/propagators/cumulative/utils/util.rs index aebcc0fb6..8fb1c0513 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/utils/util.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/utils/util.rs @@ -57,11 +57,19 @@ pub(crate) fn register_tasks< Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, >( tasks: &[Rc>], + capacity: &CVar, mut context: PropagatorConstructorContext<'_>, register_backtrack: bool, ) { + context.register( + capacity.clone(), + DomainEvents::create_with_int_events(enum_set!(DomainEvent::UpperBound)), + LocalId::from(tasks.len() as u32), + ); + tasks.iter().for_each(|task| { context.register( task.start_variable.clone(), @@ -70,6 +78,17 @@ pub(crate) fn register_tasks< )), task.id, ); + context.register( + task.processing_time.clone(), + DomainEvents::create_with_int_events(enum_set!(DomainEvent::LowerBound)), + task.id, + ); + context.register( + task.resource_usage.clone(), + DomainEvents::create_with_int_events(enum_set!(DomainEvent::LowerBound)), + task.id, + ); + if register_backtrack { context.register_for_backtrack_events( task.start_variable.clone(), diff --git a/pumpkin-macros/src/lib.rs b/pumpkin-macros/src/lib.rs index 00e8fadc4..4d1825d17 100644 --- a/pumpkin-macros/src/lib.rs +++ b/pumpkin-macros/src/lib.rs @@ -65,6 +65,36 @@ pub fn cumulative(item: TokenStream) -> TokenStream { } .into(); output.extend(stream); + + let test_name_var = format_ident!( + "{}_var", + stringcase::snake_case( + &[ + "cumulative", + &propagation_method, + explanation_type, + &option_string, + ] + .into_iter() + .filter(|string| !string.is_empty()) + .join("_") + ) + ); + let stream: TokenStream = quote! { + mzn_test!( + #test_name_var, + "cumulative_var", + vec![ + "--cumulative-propagation-method".to_string(), + stringcase::kebab_case(#propagation_method), + "--cumulative-explanation-type".to_string(), + #explanation_type.to_string(), + #(#options.to_string()),* + ] + ); + } + .into(); + output.extend(stream); } } diff --git a/pumpkin-solver/src/bin/pumpkin-solver/flatzinc/compiler/post_constraints.rs b/pumpkin-solver/src/bin/pumpkin-solver/flatzinc/compiler/post_constraints.rs index ff1dac834..a8d8c4ebc 100644 --- a/pumpkin-solver/src/bin/pumpkin-solver/flatzinc/compiler/post_constraints.rs +++ b/pumpkin-solver/src/bin/pumpkin-solver/flatzinc/compiler/post_constraints.rs @@ -30,34 +30,126 @@ pub(crate) fn run( let is_satisfiable: bool = match id.as_str() { "array_int_maximum" => compile_array_int_maximum(context, exprs, constraint_tag)?, "array_int_minimum" => compile_array_int_minimum(context, exprs, constraint_tag)?, - "int_max" => { - compile_ternary_int_predicate(context, exprs, annos, "int_max", constraint_tag, |a, b, c, constraint_tag| { - constraints::maximum([a, b], c, constraint_tag) - })? - } - "int_min" => { - compile_ternary_int_predicate(context, exprs, annos, "int_min", constraint_tag, |a, b, c, constraint_tag| { - constraints::minimum([a, b], c, constraint_tag) - })? - } + "int_max" => compile_ternary_int_predicate( + context, + exprs, + annos, + "int_max", + constraint_tag, + |a, b, c, constraint_tag| constraints::maximum([a, b], c, constraint_tag), + )?, + "int_min" => compile_ternary_int_predicate( + context, + exprs, + annos, + "int_min", + constraint_tag, + |a, b, c, constraint_tag| constraints::minimum([a, b], c, constraint_tag), + )?, // We rewrite `array_int_element` to `array_var_int_element`. "array_int_element" => compile_array_var_int_element(context, exprs, constraint_tag)?, - "array_var_int_element" => compile_array_var_int_element(context, exprs, constraint_tag)?, - - "int_eq_imp" => compile_binary_int_imp(context, exprs, annos, "int_eq_imp", constraint_tag, constraints::binary_equals)?, - "int_ge_imp" => compile_binary_int_imp(context, exprs, annos, "int_ge_imp", constraint_tag, constraints::binary_greater_than_or_equals)?, - "int_gt_imp" => compile_binary_int_imp(context, exprs, annos, "int_gt_imp", constraint_tag, constraints::binary_greater_than)?, - "int_le_imp" => compile_binary_int_imp(context, exprs, annos, "int_le_imp", constraint_tag, constraints::binary_less_than_or_equals)?, - "int_lt_imp" => compile_binary_int_imp(context, exprs, annos, "int_lt_imp", constraint_tag, constraints::binary_less_than)?, - "int_ne_imp" => compile_binary_int_imp(context, exprs, annos, "int_ne_imp", constraint_tag, constraints::binary_not_equals)?, - - "int_lin_eq_imp" => compile_int_lin_imp_predicate(context, exprs, annos, "int_lin_eq_imp", constraint_tag, constraints::equals)?, - "int_lin_ge_imp" => compile_int_lin_imp_predicate(context, exprs, annos, "int_lin_ge_imp", constraint_tag, constraints::greater_than_or_equals)?, - "int_lin_gt_imp" => compile_int_lin_imp_predicate(context, exprs, annos, "int_lin_gt_imp", constraint_tag, constraints::greater_than)?, - "int_lin_le_imp" => compile_int_lin_imp_predicate(context, exprs, annos, "int_lin_le_imp", constraint_tag, constraints::less_than_or_equals)?, - "int_lin_lt_imp" => compile_int_lin_imp_predicate(context, exprs, annos, "int_lin_lt_imp", constraint_tag, constraints::less_than)?, - "int_lin_ne_imp" => compile_int_lin_imp_predicate(context, exprs, annos, "int_lin_ne_imp", constraint_tag, constraints::not_equals)?, + "array_var_int_element" => { + compile_array_var_int_element(context, exprs, constraint_tag)? + } + + "int_eq_imp" => compile_binary_int_imp( + context, + exprs, + annos, + "int_eq_imp", + constraint_tag, + constraints::binary_equals, + )?, + "int_ge_imp" => compile_binary_int_imp( + context, + exprs, + annos, + "int_ge_imp", + constraint_tag, + constraints::binary_greater_than_or_equals, + )?, + "int_gt_imp" => compile_binary_int_imp( + context, + exprs, + annos, + "int_gt_imp", + constraint_tag, + constraints::binary_greater_than, + )?, + "int_le_imp" => compile_binary_int_imp( + context, + exprs, + annos, + "int_le_imp", + constraint_tag, + constraints::binary_less_than_or_equals, + )?, + "int_lt_imp" => compile_binary_int_imp( + context, + exprs, + annos, + "int_lt_imp", + constraint_tag, + constraints::binary_less_than, + )?, + "int_ne_imp" => compile_binary_int_imp( + context, + exprs, + annos, + "int_ne_imp", + constraint_tag, + constraints::binary_not_equals, + )?, + + "int_lin_eq_imp" => compile_int_lin_imp_predicate( + context, + exprs, + annos, + "int_lin_eq_imp", + constraint_tag, + constraints::equals, + )?, + "int_lin_ge_imp" => compile_int_lin_imp_predicate( + context, + exprs, + annos, + "int_lin_ge_imp", + constraint_tag, + constraints::greater_than_or_equals, + )?, + "int_lin_gt_imp" => compile_int_lin_imp_predicate( + context, + exprs, + annos, + "int_lin_gt_imp", + constraint_tag, + constraints::greater_than, + )?, + "int_lin_le_imp" => compile_int_lin_imp_predicate( + context, + exprs, + annos, + "int_lin_le_imp", + constraint_tag, + constraints::less_than_or_equals, + )?, + "int_lin_lt_imp" => compile_int_lin_imp_predicate( + context, + exprs, + annos, + "int_lin_lt_imp", + constraint_tag, + constraints::less_than, + )?, + "int_lin_ne_imp" => compile_int_lin_imp_predicate( + context, + exprs, + annos, + "int_lin_ne_imp", + constraint_tag, + constraints::not_equals, + )?, "int_lin_ne" => compile_int_lin_predicate( context, @@ -91,9 +183,14 @@ pub(crate) fn run( constraint_tag, constraints::less_than_or_equals, )?, - "int_lin_eq" => { - compile_int_lin_predicate(context, exprs, annos, "int_lin_eq", constraint_tag, constraints::equals)? - } + "int_lin_eq" => compile_int_lin_predicate( + context, + exprs, + annos, + "int_lin_eq", + constraint_tag, + constraints::equals, + )?, "int_lin_eq_reif" => compile_reified_int_lin_predicate( context, exprs, @@ -167,9 +264,14 @@ pub(crate) fn run( constraints::binary_less_than, )?, - "int_plus" => { - compile_ternary_int_predicate(context, exprs, annos, "int_plus", constraint_tag, constraints::plus)? - } + "int_plus" => compile_ternary_int_predicate( + context, + exprs, + annos, + "int_plus", + constraint_tag, + constraints::plus, + )?, "int_times" => compile_ternary_int_predicate( context, @@ -196,30 +298,34 @@ pub(crate) fn run( constraints::absolute, )?, - "pumpkin_all_different" => compile_all_different(context, exprs, annos, constraint_tag)?, + "pumpkin_all_different" => { + compile_all_different(context, exprs, annos, constraint_tag)? + } "pumpkin_table_int" => compile_table(context, exprs, annos, constraint_tag)?, "pumpkin_table_int_reif" => compile_table_reif(context, exprs, annos, constraint_tag)?, "array_bool_and" => compile_array_bool_and(context, exprs, constraint_tag)?, - "array_bool_element" => { - compile_array_var_bool_element(context, exprs, "array_bool_element", constraint_tag)? - } - "array_var_bool_element" => { - compile_array_var_bool_element(context, exprs, "array_var_bool_element", constraint_tag)? - } + "array_bool_element" => compile_array_var_bool_element( + context, + exprs, + "array_bool_element", + constraint_tag, + )?, + "array_var_bool_element" => compile_array_var_bool_element( + context, + exprs, + "array_var_bool_element", + constraint_tag, + )?, "array_bool_or" => compile_bool_or(context, exprs, constraint_tag)?, "pumpkin_bool_xor" => compile_bool_xor(context, exprs, constraint_tag)?, "pumpkin_bool_xor_reif" => compile_bool_xor_reif(context, exprs, constraint_tag)?, "bool2int" => compile_bool2int(context, exprs, constraint_tag)?, - "bool_lin_eq" => { - compile_bool_lin_eq_predicate(context, exprs, constraint_tag)? - } + "bool_lin_eq" => compile_bool_lin_eq_predicate(context, exprs, constraint_tag)?, - "bool_lin_le" => { - compile_bool_lin_le_predicate(context, exprs, constraint_tag)? - } + "bool_lin_le" => compile_bool_lin_le_predicate(context, exprs, constraint_tag)?, "bool_and" => compile_bool_and(context, exprs, constraint_tag)?, "bool_clause" => compile_bool_clause(context, exprs, constraint_tag)?, @@ -234,7 +340,9 @@ pub(crate) fn run( } "pumpkin_cumulative" => compile_cumulative(context, exprs, options, constraint_tag)?, - "pumpkin_cumulative_var" => todo!("The `cumulative` constraint with variable duration/resource consumption/bound is not implemented yet!"), + "pumpkin_cumulative_vars" => { + compile_cumulative_vars(context, exprs, options, constraint_tag)? + } unknown => todo!("unsupported constraint {unknown}"), }; @@ -292,6 +400,40 @@ fn compile_cumulative( Ok(post_result.is_ok()) } +fn compile_cumulative_vars( + context: &mut CompilationContext<'_>, + exprs: &[flatzinc::Expr], + options: &FlatZincOptions, + constraint_tag: ConstraintTag, +) -> Result { + check_parameters!(exprs, 4, "pumpkin_cumulative_vars"); + + let start_times = context.resolve_integer_variable_array(&exprs[0])?; + let durations = context.resolve_integer_variable_array(&exprs[1])?; + let resource_requirements = context.resolve_integer_variable_array(&exprs[2])?; + let resource_capacity = context.resolve_integer_variable(&exprs[3])?; + + let post_result = constraints::cumulative_with_options( + start_times + .iter() + .zip(durations.iter()) + .zip(resource_requirements.iter()) + .map( + |((&start_time, &processing_time), &resource_usage)| ArgTask { + start_time, + processing_time, + resource_usage, + }, + ) + .collect::>(), + resource_capacity, + options.cumulative_options, + constraint_tag, + ) + .post(context.solver); + Ok(post_result.is_ok()) +} + fn compile_array_int_maximum( context: &mut CompilationContext<'_>, exprs: &[flatzinc::Expr], diff --git a/pumpkin-solver/tests/mzn_constraints/cumulative_var.expected b/pumpkin-solver/tests/mzn_constraints/cumulative_var.expected new file mode 100644 index 000000000..b3ba0474f --- /dev/null +++ b/pumpkin-solver/tests/mzn_constraints/cumulative_var.expected @@ -0,0 +1,1101 @@ +s1 = 2; +s2 = 3; +s3 = 1; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 5; +s2 = 3; +s3 = 1; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 3; +s3 = 2; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 5; +s2 = 3; +s3 = 2; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 3; +s3 = 5; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 2; +s2 = 3; +s3 = 5; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 2; +s2 = 3; +s3 = 1; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 5; +s2 = 3; +s3 = 1; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 3; +s3 = 2; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 5; +s2 = 3; +s3 = 2; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 3; +s3 = 5; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 2; +s2 = 3; +s3 = 5; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 2; +s2 = 4; +s3 = 1; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 2; +s2 = 4; +s3 = 1; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 3; +s2 = 4; +s3 = 1; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 3; +s2 = 4; +s3 = 1; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 4; +s3 = 2; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 4; +s3 = 2; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 3; +s2 = 4; +s3 = 2; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 3; +s2 = 4; +s3 = 2; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 4; +s3 = 3; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 4; +s3 = 3; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 2; +s2 = 4; +s3 = 3; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 2; +s2 = 4; +s3 = 3; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 2; +s2 = 5; +s3 = 1; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 3; +s2 = 5; +s3 = 1; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 4; +s2 = 5; +s3 = 1; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 5; +s3 = 2; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 3; +s2 = 5; +s3 = 2; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 4; +s2 = 5; +s3 = 2; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 5; +s3 = 3; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 2; +s2 = 5; +s3 = 3; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 4; +s2 = 5; +s3 = 3; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 5; +s3 = 4; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 2; +s2 = 5; +s3 = 4; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 3; +s2 = 5; +s3 = 4; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 2; +s2 = 5; +s3 = 1; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 3; +s2 = 5; +s3 = 1; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 4; +s2 = 5; +s3 = 1; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 5; +s3 = 2; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 3; +s2 = 5; +s3 = 2; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 4; +s2 = 5; +s3 = 2; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 5; +s3 = 3; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 2; +s2 = 5; +s3 = 3; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 4; +s2 = 5; +s3 = 3; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 5; +s3 = 4; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 2; +s2 = 5; +s3 = 4; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 3; +s2 = 5; +s3 = 4; +p1 = 1; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 3; +s3 = 5; +p1 = 2; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 3; +s3 = 5; +p1 = 2; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 5; +s2 = 3; +s3 = 1; +p1 = 2; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 5; +s2 = 3; +s3 = 1; +p1 = 2; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 5; +s2 = 3; +s3 = 2; +p1 = 2; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 5; +s2 = 3; +s3 = 2; +p1 = 2; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 4; +s3 = 3; +p1 = 2; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 4; +s3 = 3; +p1 = 2; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 2; +s2 = 4; +s3 = 1; +p1 = 2; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 2; +s2 = 4; +s3 = 1; +p1 = 2; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 5; +s3 = 3; +p1 = 2; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 5; +s3 = 3; +p1 = 2; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 5; +s3 = 4; +p1 = 2; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 5; +s3 = 4; +p1 = 2; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 2; +s2 = 5; +s3 = 1; +p1 = 2; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 2; +s2 = 5; +s3 = 1; +p1 = 2; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 2; +s2 = 5; +s3 = 4; +p1 = 2; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 2; +s2 = 5; +s3 = 4; +p1 = 2; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 3; +s2 = 5; +s3 = 1; +p1 = 2; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 3; +s2 = 5; +s3 = 1; +p1 = 2; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 3; +s2 = 5; +s3 = 2; +p1 = 2; +p2 = 2; +p3 = 1; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 3; +s2 = 5; +s3 = 2; +p1 = 2; +p2 = 2; +p3 = 1; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 5; +s2 = 3; +s3 = 1; +p1 = 1; +p2 = 2; +p3 = 2; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 5; +s2 = 3; +s3 = 1; +p1 = 1; +p2 = 2; +p3 = 2; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 3; +s3 = 5; +p1 = 1; +p2 = 2; +p3 = 2; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 3; +s3 = 5; +p1 = 1; +p2 = 2; +p3 = 2; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 2; +s2 = 3; +s3 = 5; +p1 = 1; +p2 = 2; +p3 = 2; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 2; +s2 = 3; +s3 = 5; +p1 = 1; +p2 = 2; +p3 = 2; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 3; +s2 = 4; +s3 = 1; +p1 = 1; +p2 = 2; +p3 = 2; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 3; +s2 = 4; +s3 = 1; +p1 = 1; +p2 = 2; +p3 = 2; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 4; +s3 = 2; +p1 = 1; +p2 = 2; +p3 = 2; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 4; +s3 = 2; +p1 = 1; +p2 = 2; +p3 = 2; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 3; +s2 = 5; +s3 = 1; +p1 = 1; +p2 = 2; +p3 = 2; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 3; +s2 = 5; +s3 = 1; +p1 = 1; +p2 = 2; +p3 = 2; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 4; +s2 = 5; +s3 = 1; +p1 = 1; +p2 = 2; +p3 = 2; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 4; +s2 = 5; +s3 = 1; +p1 = 1; +p2 = 2; +p3 = 2; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 5; +s3 = 2; +p1 = 1; +p2 = 2; +p3 = 2; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 5; +s3 = 2; +p1 = 1; +p2 = 2; +p3 = 2; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 4; +s2 = 5; +s3 = 2; +p1 = 1; +p2 = 2; +p3 = 2; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 4; +s2 = 5; +s3 = 2; +p1 = 1; +p2 = 2; +p3 = 2; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 5; +s3 = 3; +p1 = 1; +p2 = 2; +p3 = 2; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 5; +s3 = 3; +p1 = 1; +p2 = 2; +p3 = 2; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 2; +s2 = 5; +s3 = 3; +p1 = 1; +p2 = 2; +p3 = 2; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 2; +s2 = 5; +s3 = 3; +p1 = 1; +p2 = 2; +p3 = 2; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 5; +s2 = 3; +s3 = 1; +p1 = 2; +p2 = 2; +p3 = 2; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 5; +s2 = 3; +s3 = 1; +p1 = 2; +p2 = 2; +p3 = 2; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 3; +s3 = 5; +p1 = 2; +p2 = 2; +p3 = 2; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 3; +s3 = 5; +p1 = 2; +p2 = 2; +p3 = 2; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 3; +s2 = 5; +s3 = 1; +p1 = 2; +p2 = 2; +p3 = 2; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 3; +s2 = 5; +s3 = 1; +p1 = 2; +p2 = 2; +p3 = 2; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 5; +s3 = 3; +p1 = 2; +p2 = 2; +p3 = 2; +r1 = 3; +r2 = 2; +r3 = 4; +c = 4; +---------- +s1 = 1; +s2 = 5; +s3 = 3; +p1 = 2; +p2 = 2; +p3 = 2; +r1 = 4; +r2 = 2; +r3 = 4; +c = 4; +---------- +========== diff --git a/pumpkin-solver/tests/mzn_constraints/cumulative_var.fzn b/pumpkin-solver/tests/mzn_constraints/cumulative_var.fzn new file mode 100644 index 000000000..f3a8f9671 --- /dev/null +++ b/pumpkin-solver/tests/mzn_constraints/cumulative_var.fzn @@ -0,0 +1,17 @@ +var 1..5: s1:: output_var; +var 3..5: s2:: output_var; +var 1..5: s3:: output_var; + +var 1..2: p1:: output_var; +var 2..2: p2:: output_var; +var 1..2: p3:: output_var; + +var 3..4: r1:: output_var; +var 2..2: r2:: output_var; +var 4..5: r3:: output_var; + +var 1..4: c:: output_var; + +constraint pumpkin_cumulative_vars([s1, s2, s3], [p1, p2, p3], [r1, r2, r3], c); + +solve satisfy; diff --git a/pumpkin-solver/tests/mzn_constraints/cumulative_var.template b/pumpkin-solver/tests/mzn_constraints/cumulative_var.template new file mode 100644 index 000000000..1747124b9 --- /dev/null +++ b/pumpkin-solver/tests/mzn_constraints/cumulative_var.template @@ -0,0 +1,23 @@ +predicate gecode_cumulatives(array [int] of var int: start_times, + array [int] of var int: durations, + array [int] of var int: resource_requirements, + var int: resource_capacity +); + +var 1..5: s1:: output_var; +var 3..5: s2:: output_var; +var 1..5: s3:: output_var; + +var 1..2: p1:: output_var; +var 2..2: p2:: output_var; +var 1..2: p3:: output_var; + +var 3..4: r1:: output_var; +var 2..2: r2:: output_var; +var 4..5: r3:: output_var; + +var 1..4: c:: output_var; + +constraint gecode_cumulatives([s1, s2, s3], [p1, p2, p3], [r1, r2, r3], c); + +solve satisfy; From 3e8a6e4cb07f09535054dfc2a1efd5fca074c6b7 Mon Sep 17 00:00:00 2001 From: Imko Marijnissen Date: Tue, 16 Sep 2025 12:30:44 +0900 Subject: [PATCH 05/17] fix: include resource usage + processing time in the reason of propagation + fix enqueueing --- .../time_table/explanations/big_step.rs | 16 ++++++++++++++++ .../cumulative/time_table/explanations/naive.rs | 16 ++++++++++++++++ .../time_table/explanations/pointwise.rs | 16 ++++++++++++++++ .../cumulative/time_table/time_table_util.rs | 17 ----------------- 4 files changed, 48 insertions(+), 17 deletions(-) diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/big_step.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/big_step.rs index f07fc0bdb..756d25041 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/big_step.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/big_step.rs @@ -30,6 +30,14 @@ pub(crate) fn create_big_step_propagation_explanation< >= profile.end - context.lower_bound(&profile_task.processing_time) + 1 ), predicate!(profile_task.start_variable <= profile.start), + predicate!( + profile_task.processing_time + >= context.lower_bound(&profile_task.processing_time) + ), + predicate!( + profile_task.resource_usage + >= context.lower_bound(&profile_task.resource_usage) + ), ] }) .collect() @@ -57,6 +65,14 @@ pub(crate) fn create_big_step_conflict_explanation< + 1 ), predicate!(profile_task.start_variable <= conflict_profile.start), + predicate!( + profile_task.processing_time + >= context.lower_bound(&profile_task.processing_time) + ), + predicate!( + profile_task.resource_usage + >= context.lower_bound(&profile_task.resource_usage) + ), ] }) .collect() diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/naive.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/naive.rs index 607e0bb1a..b505c0871 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/naive.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/naive.rs @@ -32,6 +32,14 @@ pub(crate) fn create_naive_propagation_explanation< profile_task.start_variable <= context.upper_bound(&profile_task.start_variable) ), + predicate!( + profile_task.processing_time + >= context.lower_bound(&profile_task.processing_time) + ), + predicate!( + profile_task.resource_usage + >= context.lower_bound(&profile_task.resource_usage) + ), ] }) .collect() @@ -61,6 +69,14 @@ pub(crate) fn create_naive_conflict_explanation< profile_task.start_variable <= context.upper_bound(&profile_task.start_variable) ), + predicate!( + profile_task.processing_time + >= context.lower_bound(&profile_task.processing_time) + ), + predicate!( + profile_task.resource_usage + >= context.lower_bound(&profile_task.resource_usage) + ), ] }) .collect() diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/pointwise.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/pointwise.rs index add650e98..0f5f68590 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/pointwise.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/pointwise.rs @@ -266,6 +266,14 @@ pub(crate) fn create_pointwise_propagation_explanation< >= time_point + 1 - context.lower_bound(&profile_task.processing_time) ), predicate!(profile_task.start_variable <= time_point), + predicate!( + profile_task.processing_time + >= context.lower_bound(&profile_task.processing_time) + ), + predicate!( + profile_task.resource_usage + >= context.lower_bound(&profile_task.resource_usage) + ), ] }) .collect() @@ -298,6 +306,14 @@ pub(crate) fn create_pointwise_conflict_explanation< >= middle_point + 1 - context.lower_bound(&profile_task.processing_time) ), predicate!(profile_task.start_variable <= middle_point), + predicate!( + profile_task.processing_time + >= context.lower_bound(&profile_task.processing_time) + ), + predicate!( + profile_task.resource_usage + >= context.lower_bound(&profile_task.resource_usage) + ), ] }) .collect() diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_util.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_util.rs index 3682a5691..6c01452b0 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_util.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_util.rs @@ -53,17 +53,6 @@ pub(crate) fn should_enqueue< context: PropagationContext, empty_time_table: bool, ) -> ShouldEnqueueResult { - 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 stored 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", - updatable_structures.get_stored_lower_bound(updated_task), - context.lower_bound(&updated_task.start_variable), - updatable_structures.get_stored_upper_bound(updated_task), - context.upper_bound(&updated_task.start_variable), - ); - let mut result = ShouldEnqueueResult { decision: EnqueueDecision::Skip, update: None, @@ -72,12 +61,6 @@ pub(crate) fn should_enqueue< 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; - } - // We check whether a mandatory part was extended/introduced if has_mandatory_part(context, updated_task) { result.update = Some(UpdatedTaskInfo { From 502e2148957bfde2ba47a9fefa2efb5ad1dcc4ce Mon Sep 17 00:00:00 2001 From: Imko Marijnissen Date: Tue, 16 Sep 2025 14:13:39 +0900 Subject: [PATCH 06/17] fix: adding capacity to explanation as well + fixing returned predicate for constant --- .../core/src/engine/variables/constant.rs | 4 +- .../time_table/explanations/big_step.rs | 8 + .../time_table/explanations/naive.rs | 8 + .../time_table/explanations/pointwise.rs | 14 ++ .../synchronisation.rs | 1 + .../time_table_over_interval_incremental.rs | 36 ++--- .../synchronisation.rs | 33 +--- .../time_table_per_point_incremental.rs | 151 +++++++----------- .../time_table/propagation_handler.rs | 98 ++++++++---- .../time_table/time_table_over_interval.rs | 60 +++---- .../time_table/time_table_per_point.rs | 36 ++--- .../cumulative/time_table/time_table_util.rs | 32 +++- 12 files changed, 241 insertions(+), 240 deletions(-) diff --git a/pumpkin-crates/core/src/engine/variables/constant.rs b/pumpkin-crates/core/src/engine/variables/constant.rs index 823e73ab1..17db62254 100644 --- a/pumpkin-crates/core/src/engine/variables/constant.rs +++ b/pumpkin-crates/core/src/engine/variables/constant.rs @@ -84,7 +84,7 @@ impl PredicateConstructor for i32 { type Value = i32; fn lower_bound_predicate(&self, bound: Self::Value) -> Predicate { - if bound >= *self { + if bound <= *self { Predicate::trivially_true() } else { Predicate::trivially_false() @@ -92,7 +92,7 @@ impl PredicateConstructor for i32 { } fn upper_bound_predicate(&self, bound: Self::Value) -> Predicate { - if bound <= *self { + if bound >= *self { Predicate::trivially_true() } else { Predicate::trivially_false() diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/big_step.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/big_step.rs index 756d25041..a3e1a0cc1 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/big_step.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/big_step.rs @@ -16,9 +16,11 @@ pub(crate) fn create_big_step_propagation_explanation< Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, >( context: PropagationContext, profile: &ResourceProfile, + capacity: CVar, ) -> PropositionalConjunction { profile .profile_tasks @@ -38,8 +40,10 @@ pub(crate) fn create_big_step_propagation_explanation< profile_task.resource_usage >= context.lower_bound(&profile_task.resource_usage) ), + predicate!(capacity <= context.upper_bound(&capacity)), ] }) + .filter(|&predicate| predicate != Predicate::trivially_true()) .collect() } @@ -49,9 +53,11 @@ pub(crate) fn create_big_step_conflict_explanation< Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, >( context: PropagationContext, conflict_profile: &ResourceProfile, + capacity: CVar, ) -> PropositionalConjunction { conflict_profile .profile_tasks @@ -73,8 +79,10 @@ pub(crate) fn create_big_step_conflict_explanation< profile_task.resource_usage >= context.lower_bound(&profile_task.resource_usage) ), + predicate!(capacity <= context.upper_bound(&capacity)), ] }) + .filter(|&predicate| predicate != Predicate::trivially_true()) .collect() } diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/naive.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/naive.rs index b505c0871..115f10995 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/naive.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/naive.rs @@ -15,9 +15,11 @@ pub(crate) fn create_naive_propagation_explanation< Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, >( profile: &ResourceProfile, context: PropagationContext, + capacity: CVar, ) -> PropositionalConjunction { profile .profile_tasks @@ -40,8 +42,10 @@ pub(crate) fn create_naive_propagation_explanation< profile_task.resource_usage >= context.lower_bound(&profile_task.resource_usage) ), + predicate!(capacity <= context.upper_bound(&capacity)), ] }) + .filter(|&predicate| predicate != Predicate::trivially_true()) .collect() } @@ -51,10 +55,12 @@ pub(crate) fn create_naive_conflict_explanation< Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, Context: ReadDomains + Copy, >( conflict_profile: &ResourceProfile, context: Context, + capacity: CVar, ) -> PropositionalConjunction { conflict_profile .profile_tasks @@ -77,8 +83,10 @@ pub(crate) fn create_naive_conflict_explanation< profile_task.resource_usage >= context.lower_bound(&profile_task.resource_usage) ), + predicate!(capacity <= context.upper_bound(&capacity)), ] }) + .filter(|&predicate| predicate != Predicate::trivially_true()) .collect() } diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/pointwise.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/pointwise.rs index 0f5f68590..3681ff702 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/pointwise.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/pointwise.rs @@ -22,10 +22,12 @@ pub(crate) fn propagate_lower_bounds_with_pointwise_explanations< Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, >( context: &mut PropagationContextMut, profiles: &[&ResourceProfile], propagating_task: &Rc>, + capacity: CVar, inference_code: InferenceCode, ) -> Result<(), EmptyDomain> { // The time points should follow the following properties (based on `Improving @@ -69,6 +71,7 @@ pub(crate) fn propagate_lower_bounds_with_pointwise_explanations< context.as_readonly(), time_point, profiles[current_profile_index], + capacity.clone(), ), CumulativeExplanationType::Pointwise, context.as_readonly(), @@ -137,10 +140,12 @@ pub(crate) fn propagate_upper_bounds_with_pointwise_explanations< Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, >( context: &mut PropagationContextMut, profiles: &[&ResourceProfile], propagating_task: &Rc>, + capacity: CVar, inference_code: InferenceCode, ) -> Result<(), EmptyDomain> { // The time points should follow the following properties (based on `Improving @@ -183,6 +188,7 @@ pub(crate) fn propagate_upper_bounds_with_pointwise_explanations< context.as_readonly(), time_point, profiles[current_profile_index], + capacity.clone(), ), CumulativeExplanationType::Pointwise, context.as_readonly(), @@ -251,10 +257,12 @@ pub(crate) fn create_pointwise_propagation_explanation< Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, >( context: PropagationContext, time_point: i32, profile: &ResourceProfile, + capacity: CVar, ) -> PropositionalConjunction { profile .profile_tasks @@ -274,8 +282,10 @@ pub(crate) fn create_pointwise_propagation_explanation< profile_task.resource_usage >= context.lower_bound(&profile_task.resource_usage) ), + predicate!(capacity <= context.upper_bound(&capacity)), ] }) + .filter(|&predicate| predicate != Predicate::trivially_true()) .collect() } @@ -285,9 +295,11 @@ pub(crate) fn create_pointwise_conflict_explanation< Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, >( context: PropagationContext, conflict_profile: &ResourceProfile, + capacity: CVar, ) -> PropositionalConjunction { // As stated in improving scheduling by learning, we choose the middle point; this // could potentially be improved @@ -314,8 +326,10 @@ pub(crate) fn create_pointwise_conflict_explanation< profile_task.resource_usage >= context.lower_bound(&profile_task.resource_usage) ), + predicate!(capacity <= context.upper_bound(&capacity)), ] }) + .filter(|&predicate| predicate != Predicate::trivially_true()) .collect() } diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/synchronisation.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/synchronisation.rs index c5bbbb4a9..183db10b9 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/synchronisation.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/synchronisation.rs @@ -132,6 +132,7 @@ pub(crate) fn create_synchronised_conflict_explanation< height: resource_usage, }, parameters.options.explanation_type, + parameters.capacity.clone(), ) .into()) } diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/time_table_over_interval_incremental.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/time_table_over_interval_incremental.rs index 797c1931b..efa4706c5 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/time_table_over_interval_incremental.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/time_table_over_interval_incremental.rs @@ -233,6 +233,7 @@ impl< self.inference_code, &conflict_tasks, self.parameters.options.explanation_type, + self.parameters.capacity.clone(), ) .into())); } @@ -407,6 +408,7 @@ impl< self.inference_code, conflicting_profile, self.parameters.options.explanation_type, + self.parameters.capacity.clone(), ) .into()); } @@ -698,7 +700,6 @@ fn find_overlapping_profile< #[cfg(test)] mod tests { - use crate::basic_types::Inconsistency; use crate::conjunction; use crate::constraint_arguments::CumulativeExplanationType; use crate::engine::predicates::predicate::Predicate; @@ -786,25 +787,20 @@ mod tests { constraint_tag, )); - assert!(matches!(result, Err(Inconsistency::Conflict(_)))); - assert!(match result { - Err(Inconsistency::Conflict(conflict)) => { - let expected = [ - predicate!(s1 <= 1), - predicate!(s1 >= 1), - predicate!(s2 >= 1), - predicate!(s2 <= 1), - ]; - expected.iter().all(|y| { - conflict - .conjunction - .iter() - .collect::>() - .contains(&y) - }) && conflict.conjunction.iter().all(|y| expected.contains(y)) - } - _ => false, - }); + assert!(result.is_err()); + let reason = solver.get_reason_int(Predicate::trivially_false()); + let expected = [ + predicate!(s1 <= 1), + predicate!(s1 >= 1), + predicate!(s2 >= 1), + predicate!(s2 <= 1), + ]; + assert!( + expected + .iter() + .all(|y| { reason.iter().collect::>().contains(&y) }) + && reason.iter().all(|y| expected.contains(y)) + ); } #[test] diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/synchronisation.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/synchronisation.rs index a5bc5bc2e..25d56e1e6 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/synchronisation.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/synchronisation.rs @@ -1,12 +1,9 @@ use std::rc::Rc; -use crate::basic_types::Inconsistency; use crate::basic_types::PropagationStatusCP; use crate::engine::cp::propagation::contexts::propagation_context::ReadDomains; use crate::engine::propagation::PropagationContext; -use crate::engine::propagation::PropagationContextMut; use crate::proof::InferenceCode; -use crate::propagators::create_time_table_per_point_from_scratch; use crate::propagators::cumulative::time_table::propagation_handler::create_explanation_profile_height; use crate::propagators::CumulativeParameters; use crate::propagators::PerPointTimeTableType; @@ -15,35 +12,6 @@ use crate::propagators::Task; use crate::pumpkin_assert_moderate; use crate::variables::IntegerVariable; -/// Returns whether the synchronised conflict explanation created by -/// [`TimeTablePerPointIncrementalPropgator`] is the same as that created by -/// [`TimeTablePerPointPropagator`]. -pub(crate) fn check_synchronisation_conflict_explanation_per_point< - Var: IntegerVariable + 'static, - PVar: IntegerVariable + 'static, - RVar: IntegerVariable + 'static, - CVar: IntegerVariable + 'static, ->( - synchronised_conflict_explanation: &PropagationStatusCP, - context: &mut PropagationContextMut, - inference_code: InferenceCode, - parameters: &CumulativeParameters, -) -> bool { - let error_from_scratch = - create_time_table_per_point_from_scratch(context, inference_code, parameters); - if let Err(Inconsistency::Conflict(explanation_scratch)) = error_from_scratch { - if let Err(Inconsistency::Conflict(conflict)) = &synchronised_conflict_explanation { - // We check whether both inconsistencies are of the same type and then we check their - // corresponding explanations - conflict.conjunction == explanation_scratch.conjunction - } else { - false - } - } else { - false - } -} - /// Finds the conflicting profile which would have been found by the /// [`TimeTablePerPointPropagator`]; this is the conflicting profile which has the minimum maximum /// ID in set of the first `n` profile tasks (when sorted on ID) which overflow the capacity @@ -168,6 +136,7 @@ pub(crate) fn create_synchronised_conflict_explanation< height: new_height, }, parameters.options.explanation_type, + parameters.capacity.clone(), ) .into()) } diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs index b441dffe6..f218b4619 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs @@ -22,7 +22,6 @@ use crate::engine::variables::IntegerVariable; use crate::proof::ConstraintTag; use crate::proof::InferenceCode; use crate::propagators::create_time_table_per_point_from_scratch; -use crate::propagators::cumulative::time_table::per_point_incremental_propagator::synchronisation::check_synchronisation_conflict_explanation_per_point; use crate::propagators::cumulative::time_table::per_point_incremental_propagator::synchronisation::create_synchronised_conflict_explanation; use crate::propagators::cumulative::time_table::per_point_incremental_propagator::synchronisation::find_synchronised_conflict; use crate::propagators::cumulative::time_table::per_point_incremental_propagator::synchronisation::synchronise_time_table; @@ -234,6 +233,7 @@ impl< self.inference_code, current_profile, self.parameters.options.explanation_type, + self.parameters.capacity.clone(), ) .into())); } @@ -380,16 +380,6 @@ impl< &self.parameters, ); - pumpkin_assert_extreme!( - check_synchronisation_conflict_explanation_per_point( - &synchronised_conflict_explanation, - context, - self.inference_code, - &self.parameters, - ), - "The conflict explanation was not the same as the conflict explanation from scratch!" - ); - // We have found the previous conflict self.found_previous_conflict = true; @@ -423,6 +413,7 @@ impl< self.inference_code, conflicting_profile, self.parameters.options.explanation_type, + self.parameters.capacity.clone(), ) .into()); } @@ -683,6 +674,7 @@ mod debug { #[cfg(test)] mod tests { use crate::basic_types::Inconsistency; + use crate::basic_types::PropagatorConflict; use crate::conjunction; use crate::constraint_arguments::CumulativeExplanationType; use crate::engine::predicates::predicate::Predicate; @@ -771,23 +763,21 @@ mod tests { }, constraint_tag, )); - assert!(match result { - Err(Inconsistency::Conflict(x)) => { - let expected = [ - predicate!(s1 <= 1), - predicate!(s1 >= 1), - predicate!(s2 <= 1), - predicate!(s2 >= 1), - ]; - expected.iter().all(|y| { - x.conjunction - .iter() - .collect::>() - .contains(&y) - }) && x.conjunction.iter().all(|y| expected.contains(y)) - } - _ => false, - }); + + assert!(result.is_err()); + let reason = solver.get_reason_int(Predicate::trivially_false()); + let expected = [ + predicate!(s1 <= 1), + predicate!(s1 >= 1), + predicate!(s2 >= 1), + predicate!(s2 <= 1), + ]; + assert!( + expected + .iter() + .all(|y| { reason.iter().collect::>().contains(&y) }) + && reason.iter().all(|y| expected.contains(y)) + ); } #[test] @@ -1409,24 +1399,16 @@ mod tests { let _ = solver.increase_lower_bound_and_notify(propagator, 2, s3, 7); let _ = solver.increase_lower_bound_and_notify(propagator, 1, s2, 7); let result = solver.propagate(propagator); - assert!({ - let same = if let Err(Inconsistency::Conflict (conflict - )) = - &result - { - if let Err(Inconsistency::Conflict (explanation_scratch)) = - &result_scratch - { - conflict.conjunction.iter().collect::>() - == explanation_scratch.conjunction.iter().collect::>() - } else { - false - } - } else { - false - }; - same - }, "The results are different than expected - Expected: {result_scratch:?} but was: {result:?}"); + + assert!(result.is_err()); + assert!(result_scratch.is_err()); + + let reason_scratch = solver_scratch.get_reason_int(Predicate::trivially_false()); + if let Err(Inconsistency::Conflict(conflict)) = &result { + assert_eq!(conflict.conjunction, reason_scratch) + } else { + panic!() + } } #[test] @@ -1517,12 +1499,10 @@ mod tests { let result_scratch = solver_scratch.propagate(propagator_scratch); assert!(result_scratch.is_err()); + let reason_scratch = solver_scratch.get_reason_int(Predicate::trivially_false()); + if let Err(Inconsistency::Conflict(explanation)) = &result { - if let Err(Inconsistency::Conflict(explanation_scratch)) = &result_scratch { - assert_eq!(explanation.conjunction, explanation_scratch.conjunction) - } else { - panic!("Synchronised version found a conflict while the non-synchronised version did not"); - } + assert_eq!(explanation.conjunction, reason_scratch) } else { panic!("Synchronised version did not find a conflict"); } @@ -1613,19 +1593,20 @@ mod tests { let _ = solver.increase_lower_bound_and_notify(propagator, 1, s2, 7); let result = solver.propagate(propagator); let result_scratch = solver_scratch.propagate(propagator_scratch); - assert!({ - let same = if let Err(Inconsistency::Conflict(explanation)) = &result { - if let Err(Inconsistency::Conflict(explanation_scratch)) = &result_scratch { - explanation.conjunction.iter().collect::>() - != explanation_scratch.conjunction.iter().collect::>() - } else { - false - } - } else { - false - }; - same - }); + + assert!(result.is_err()); + assert!(result_scratch.is_err()); + + let reason_scratch = solver_scratch.get_reason_int(Predicate::trivially_false()); + + if let Err(Inconsistency::Conflict(conflict)) = result { + assert_ne!( + conflict.conjunction.iter().collect::>(), + reason_scratch.iter().collect::>() + ); + } else { + panic!() + } } #[test] @@ -1923,26 +1904,20 @@ mod tests { assert!(result_scratch.is_err()); let result = solver.propagate(propagator); assert!(result.is_err()); - if let ( - Err(Inconsistency::Conflict(explanation)), - Err(Inconsistency::Conflict(explanation_scratch)), - ) = (result, result_scratch) - { - assert_ne!(explanation, explanation_scratch); - let explanation_vec = explanation.conjunction.iter().cloned().collect::>(); - let explanation_scratch_vec = explanation_scratch - .conjunction - .iter() - .cloned() - .collect::>(); - println!("{explanation_vec:?}"); - println!("{explanation_scratch_vec:?}"); + let reason_scratch = solver_scratch.get_reason_int(Predicate::trivially_false()); + + if let Err(Inconsistency::Conflict(PropagatorConflict { + conjunction, + inference_code: _, + })) = result + { + assert_ne!(conjunction, reason_scratch); - assert!(explanation_vec.contains(&s2.lower_bound_predicate(5))); - assert!(!explanation_scratch_vec.contains(&s2.lower_bound_predicate(5))); + assert!(conjunction.contains(s2.lower_bound_predicate(5))); + assert!(!reason_scratch.contains(s2.lower_bound_predicate(5))); } else { - panic!("Incorrect result") + panic!() } } @@ -2041,15 +2016,11 @@ mod tests { assert!(result_scratch.is_err()); let result = solver.propagate(propagator); assert!(result.is_err()); - if let ( - Err(Inconsistency::Conflict(explanation)), - Err(Inconsistency::Conflict(explanation_scratch)), - ) = (result, result_scratch) - { - assert_eq!( - explanation.conjunction.iter().collect::>(), - explanation_scratch.conjunction.iter().collect::>() - ); + + let reason_scratch = solver_scratch.get_reason_int(Predicate::trivially_false()); + + if let Err(Inconsistency::Conflict(explanation)) = result { + assert_eq!(explanation.conjunction, reason_scratch); } else { panic!("Incorrect result") } diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs index 4f26be826..3b51f9731 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs @@ -43,13 +43,9 @@ pub(crate) struct CumulativePropagationHandler { } fn check_explanation(explanation: &PropositionalConjunction, context: PropagationContext) -> bool { - explanation.iter().all(|&predicate| { - let integer_predicate = predicate; - - context - .assignments() - .is_predicate_satisfied(integer_predicate) - }) + explanation + .iter() + .all(|&predicate| context.assignments().is_predicate_satisfied(predicate)) } impl CumulativePropagationHandler { @@ -66,16 +62,18 @@ impl CumulativePropagationHandler { /// Propagates the lower-bound of the `propagating_task` to not conflict with all of the /// `profiles` anymore. - pub(crate) fn propagate_chain_of_lower_bounds_with_explanations( + pub(crate) fn propagate_chain_of_lower_bounds_with_explanations( &mut self, context: &mut PropagationContextMut, profiles: &[&ResourceProfile], propagating_task: &Rc>, + capacity: CVar, ) -> Result<(), EmptyDomain> where Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, { pumpkin_assert_simple!(!profiles.is_empty()); match self.explanation_type { @@ -84,11 +82,17 @@ impl CumulativePropagationHandler { for profile in profiles { let explanation = match self.explanation_type { - CumulativeExplanationType::Naive => { - create_naive_propagation_explanation(profile, context.as_readonly()) - } + CumulativeExplanationType::Naive => create_naive_propagation_explanation( + profile, + context.as_readonly(), + capacity.clone(), + ), CumulativeExplanationType::BigStep => { - create_big_step_propagation_explanation(context.as_readonly(), profile) + create_big_step_propagation_explanation( + context.as_readonly(), + profile, + capacity.clone(), + ) } CumulativeExplanationType::Pointwise => { unreachable!("At the moment, we do not store the profile explanation for the pointwise explanation since it consists of multiple explanations") @@ -125,6 +129,7 @@ impl CumulativePropagationHandler { context, profiles, propagating_task, + capacity, self.inference_code, ) } @@ -133,16 +138,18 @@ impl CumulativePropagationHandler { /// Propagates the upper-bound of the `propagating_task` to not conflict with all of the /// `profiles` anymore. - pub(crate) fn propagate_chain_of_upper_bounds_with_explanations( + pub(crate) fn propagate_chain_of_upper_bounds_with_explanations( &mut self, context: &mut PropagationContextMut, profiles: &[&ResourceProfile], propagating_task: &Rc>, + capacity: CVar, ) -> Result<(), EmptyDomain> where Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, { pumpkin_assert_simple!(!profiles.is_empty()); @@ -152,11 +159,17 @@ impl CumulativePropagationHandler { for profile in profiles { let explanation = match self.explanation_type { - CumulativeExplanationType::Naive => { - create_naive_propagation_explanation(profile, context.as_readonly()) - } + CumulativeExplanationType::Naive => create_naive_propagation_explanation( + profile, + context.as_readonly(), + capacity.clone(), + ), CumulativeExplanationType::BigStep => { - create_big_step_propagation_explanation(context.as_readonly(), profile) + create_big_step_propagation_explanation( + context.as_readonly(), + profile, + capacity.clone(), + ) } CumulativeExplanationType::Pointwise => { unreachable!("At the moment, we do not store the profile explanation for the pointwise explanation since it consists of multiple explanations") @@ -194,6 +207,7 @@ impl CumulativePropagationHandler { context, profiles, propagating_task, + capacity, self.inference_code, ) } @@ -201,16 +215,18 @@ impl CumulativePropagationHandler { } /// Propagates the lower-bound of the `propagating_task` to not conflict with `profile` anymore. - pub(crate) fn propagate_lower_bound_with_explanations( + pub(crate) fn propagate_lower_bound_with_explanations( &mut self, context: &mut PropagationContextMut, profile: &ResourceProfile, propagating_task: &Rc>, + capacity: CVar, ) -> Result<(), EmptyDomain> where Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, { pumpkin_assert_advanced!( context.lower_bound(&propagating_task.start_variable) < profile.end + 1 @@ -222,7 +238,8 @@ impl CumulativePropagationHandler { // `get_stored_profile_explanation_or_init` and // `create_predicate_propagating_task_lower_bound_propagation` both use the // explanation type to create the explanations. - let explanation = self.get_stored_profile_explanation_or_init(context, profile); + let explanation = + self.get_stored_profile_explanation_or_init(context, profile, capacity); let lower_bound_predicate_propagating_task = create_predicate_propagating_task_lower_bound_propagation( self.explanation_type, @@ -246,6 +263,7 @@ impl CumulativePropagationHandler { context, &[profile], propagating_task, + capacity, self.inference_code, ) } @@ -253,16 +271,18 @@ impl CumulativePropagationHandler { } /// Propagates the upper-bound of the `propagating_task` to not conflict with `profile` anymore. - pub(crate) fn propagate_upper_bound_with_explanations( + pub(crate) fn propagate_upper_bound_with_explanations( &mut self, context: &mut PropagationContextMut, profile: &ResourceProfile, propagating_task: &Rc>, + capacity: CVar, ) -> Result<(), EmptyDomain> where Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, { pumpkin_assert_advanced!( context.upper_bound(&propagating_task.start_variable) @@ -275,7 +295,8 @@ impl CumulativePropagationHandler { // `get_stored_profile_explanation_or_init` and // `create_predicate_propagating_task_upper_bound_propagation` both use the // explanation type to create the explanations. - let explanation = self.get_stored_profile_explanation_or_init(context, profile); + let explanation = + self.get_stored_profile_explanation_or_init(context, profile, capacity); let upper_bound_predicate_propagating_task = create_predicate_propagating_task_upper_bound_propagation( self.explanation_type, @@ -303,6 +324,7 @@ impl CumulativePropagationHandler { context, &[profile], propagating_task, + capacity, self.inference_code, ) } @@ -311,16 +333,18 @@ impl CumulativePropagationHandler { /// Propagates a hole in the domain; note that this explanation does not contain any of the /// bounds of `propagating_task`. - pub(crate) fn propagate_holes_in_domain( + pub(crate) fn propagate_holes_in_domain( &mut self, context: &mut PropagationContextMut, profile: &ResourceProfile, propagating_task: &Rc>, + capacity: CVar, ) -> Result<(), EmptyDomain> where Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, { // We go through all of the time-points which cause `task` to overlap // with the resource profile @@ -357,7 +381,11 @@ impl CumulativePropagationHandler { // We use the same procedure for the explanation using naive and bigstep, note // that `get_stored_profile_explanation_or_init` uses the // explanation type to create the explanations. - let explanation = self.get_stored_profile_explanation_or_init(context, profile); + let explanation = self.get_stored_profile_explanation_or_init( + context, + profile, + capacity.clone(), + ); pumpkin_assert_extreme!(check_explanation(&explanation, context.as_readonly())); context.post( predicate![propagating_task.start_variable != time_point], @@ -390,6 +418,7 @@ impl CumulativePropagationHandler { context.as_readonly(), corresponding_profile_explanation_point, profile, + capacity.clone(), ); pumpkin_assert_extreme!(check_explanation(&explanation, context.as_readonly())); context.post( @@ -411,24 +440,26 @@ impl CumulativePropagationHandler { } /// Either we get the stored stored profile explanation or we initialize it. - fn get_stored_profile_explanation_or_init( + fn get_stored_profile_explanation_or_init( &mut self, context: &mut PropagationContextMut, profile: &ResourceProfile, + capacity: CVar, ) -> Rc where Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, { Rc::clone(self.stored_profile_explanation.get_or_init(|| { Rc::new( match self.explanation_type { CumulativeExplanationType::Naive => { - create_naive_propagation_explanation(profile, context.as_readonly()) + create_naive_propagation_explanation(profile, context.as_readonly(), capacity) }, CumulativeExplanationType::BigStep => { - create_big_step_propagation_explanation(context.as_readonly(), profile) + create_big_step_propagation_explanation(context.as_readonly(), profile, capacity) }, CumulativeExplanationType::Pointwise => { unreachable!("At the moment, we do not store the profile explanation for the pointwise explanation since it consists of multiple explanations") @@ -441,26 +472,28 @@ impl CumulativePropagationHandler { /// Creates an explanation of the conflict caused by `conflict_profile` based on the provided /// `explanation_type`. -pub(crate) fn create_explanation_profile_height( +pub(crate) fn create_explanation_profile_height( context: PropagationContext, inference_code: InferenceCode, conflict_profile: &ResourceProfile, explanation_type: CumulativeExplanationType, + capacity: CVar, ) -> PropagatorConflict where Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, + CVar: IntegerVariable + 'static, { let conjunction = match explanation_type { CumulativeExplanationType::Naive => { - create_naive_conflict_explanation(conflict_profile, context) + create_naive_conflict_explanation(conflict_profile, context, capacity) } CumulativeExplanationType::BigStep => { - create_big_step_conflict_explanation(context, conflict_profile) + create_big_step_conflict_explanation(context, conflict_profile, capacity) } CumulativeExplanationType::Pointwise => { - create_pointwise_conflict_explanation(context, conflict_profile) + create_pointwise_conflict_explanation(context, conflict_profile, capacity) } }; @@ -548,6 +581,7 @@ pub(crate) mod test_propagation_handler { self.propagation_handler.inference_code, &profile, self.propagation_handler.explanation_type, + 0, ); (reason.conjunction, y) @@ -595,6 +629,7 @@ pub(crate) mod test_propagation_handler { ), &profile, &Rc::new(propagating_task), + 1, ); assert!(result.is_ok()); assert_eq!(self.assignments.get_lower_bound(x), 19); @@ -660,6 +695,7 @@ pub(crate) mod test_propagation_handler { ), &[&profile_y, &profile_z], &Rc::new(propagating_task), + 1, ); assert!(result.is_ok()); assert_eq!(self.assignments.get_lower_bound(x), 22); @@ -711,6 +747,7 @@ pub(crate) mod test_propagation_handler { ), &profile, &Rc::new(propagating_task), + 1, ); assert!(result.is_ok()); assert_eq!(self.assignments.get_upper_bound(x), 10); @@ -776,6 +813,7 @@ pub(crate) mod test_propagation_handler { ), &[&profile_z, &profile_y], &Rc::new(propagating_task), + 1, ); assert!(result.is_ok()); assert_eq!(self.assignments.get_upper_bound(x), 3); diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_over_interval.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_over_interval.rs index d0b9775c1..488ce258e 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_over_interval.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_over_interval.rs @@ -393,8 +393,6 @@ fn create_time_table_from_events< let mut current_resource_usage: i32 = 0; // The beginning of the current interval under consideration let mut start_of_interval: i32 = -1; - // Determines whether a conflict has occurred - let mut is_conflicting = false; // We go over all the events and create the time-table for event in events { @@ -417,12 +415,6 @@ fn create_time_table_from_events< } else { // A profile is currently being created - // We have first traversed all of the ends of mandatory parts, meaning that any - // overflow will persist after processing all events at this time-point - if current_resource_usage > context.upper_bound(¶meters.capacity) { - is_conflicting = true; - } - // Potentially we need to end the current profile and start a new one due to the // addition/removal of the current task if start_of_interval != event.time_stamp { @@ -432,6 +424,8 @@ fn create_time_table_from_events< profile_tasks: current_profile_tasks.clone(), height: current_resource_usage, }; + + // Now we propagate the lower-bound of the capacity if new_profile.height > context.lower_bound(¶meters.capacity) { context.post( predicate!(parameters.capacity >= new_profile.height), @@ -440,6 +434,7 @@ fn create_time_table_from_events< inference_code, &new_profile, parameters.options.explanation_type, + parameters.capacity.clone(), ) .conjunction, inference_code, @@ -466,7 +461,6 @@ fn create_time_table_from_events< // We thus need to start a new profile start_of_interval = event.time_stamp; if event.change_in_resource_usage < 0 { - pumpkin_assert_simple!(!is_conflicting); // The mandatory part of a task has ended, we should thus remove it from the // contributing tasks let _ = current_profile_tasks.remove( @@ -484,17 +478,14 @@ fn create_time_table_from_events< !current_profile_tasks.contains(&event.task), "Task is being added to the profile while it is already part of the contributing tasks" ); - if !is_conflicting { - // If the profile is already conflicting then we shouldn't add more tasks. - // This could be changed in the future so that we can pick the tasks which - // are used for the conflict explanation - current_profile_tasks.push(event.task); - } + // If the profile is already conflicting then we shouldn't add more tasks. + // This could be changed in the future so that we can pick the tasks which + // are used for the conflict explanation + current_profile_tasks.push(event.task); } } } } - pumpkin_assert_simple!(!is_conflicting); Ok(time_table) } @@ -548,7 +539,6 @@ pub(crate) fn debug_propagate_from_scratch_time_table_interval< #[cfg(test)] mod tests { - use crate::basic_types::Inconsistency; use crate::conjunction; use crate::constraint_arguments::CumulativeExplanationType; use crate::engine::predicates::predicate::Predicate; @@ -623,28 +613,20 @@ mod tests { constraint_tag, )); - assert!(match result { - Err(e) => { - match e { - Inconsistency::EmptyDomain => false, - Inconsistency::Conflict(x) => { - let expected = [ - predicate!(s1 <= 1), - predicate!(s1 >= 1), - predicate!(s2 >= 1), - predicate!(s2 <= 1), - ]; - expected.iter().all(|y| { - x.conjunction - .iter() - .collect::>() - .contains(&y) - }) && x.conjunction.iter().all(|y| expected.contains(y)) - } - } - } - _ => false, - }); + assert!(result.is_err()); + let reason = solver.get_reason_int(Predicate::trivially_false()); + let expected = [ + predicate!(s1 <= 1), + predicate!(s1 >= 1), + predicate!(s2 >= 1), + predicate!(s2 <= 1), + ]; + assert!( + expected + .iter() + .all(|y| { reason.iter().collect::>().contains(&y) }) + && reason.iter().all(|y| expected.contains(y)) + ); } #[test] diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_per_point.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_per_point.rs index 4071e928a..0c1097d4b 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_per_point.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_per_point.rs @@ -299,6 +299,7 @@ pub(crate) fn create_time_table_per_point_from_scratch< inference_code, current_profile, parameters.options.explanation_type, + parameters.capacity.clone(), ) .conjunction, inference_code, @@ -342,7 +343,6 @@ pub(crate) fn debug_propagate_from_scratch_time_table_point< #[cfg(test)] mod tests { - use crate::basic_types::Inconsistency; use crate::conjunction; use crate::constraint_arguments::CumulativeExplanationType; use crate::engine::predicates::predicate::Predicate; @@ -416,27 +416,21 @@ mod tests { }, constraint_tag, )); - assert!(match result { - Err(e) => match e { - Inconsistency::EmptyDomain => false, - Inconsistency::Conflict(x) => { - let expected = [ - predicate!(s1 <= 1), - predicate!(s1 >= 1), - predicate!(s2 <= 1), - predicate!(s2 >= 1), - ]; - expected.iter().all(|y| { - x.conjunction - .iter() - .collect::>() - .contains(&y) - }) && x.conjunction.iter().all(|y| expected.contains(y)) - } - }, - Ok(_) => false, - }); + assert!(result.is_err()); + let reason = solver.get_reason_int(Predicate::trivially_false()); + let expected = [ + predicate!(s1 <= 1), + predicate!(s1 >= 1), + predicate!(s2 >= 1), + predicate!(s2 <= 1), + ]; + assert!( + expected + .iter() + .all(|y| { reason.iter().collect::>().contains(&y) }) + && reason.iter().all(|y| expected.contains(y)) + ); } #[test] diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_util.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_util.rs index 6c01452b0..404d5a1db 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_util.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_util.rs @@ -340,12 +340,25 @@ fn propagate_single_profiles< // For every possible update we let the propagation handler propagate let result = match possible_update { CanUpdate::LowerBound => propagation_handler - .propagate_lower_bound_with_explanations(context, profile, &task), + .propagate_lower_bound_with_explanations( + context, + profile, + &task, + parameters.capacity.clone(), + ), CanUpdate::UpperBound => propagation_handler - .propagate_upper_bound_with_explanations(context, profile, &task), - CanUpdate::Holes => { - propagation_handler.propagate_holes_in_domain(context, profile, &task) - } + .propagate_upper_bound_with_explanations( + context, + profile, + &task, + parameters.capacity.clone(), + ), + CanUpdate::Holes => propagation_handler.propagate_holes_in_domain( + context, + profile, + &task, + parameters.capacity.clone(), + ), }; if result.is_err() { updatable_structures.restore_temporarily_removed(); @@ -445,6 +458,7 @@ fn propagate_sequence_of_profiles< context, &time_table[profile_index..last_index], task, + parameters.capacity.clone(), )?; // Then we set the new profile index to the last index, note that this index (since @@ -474,6 +488,7 @@ fn propagate_sequence_of_profiles< context, &time_table[first_index..=profile_index], task, + parameters.capacity.clone(), )?; // Then we set the new profile index to maximum of the previous value of the new @@ -484,7 +499,12 @@ fn propagate_sequence_of_profiles< if parameters.options.allow_holes_in_domain { // If we allow the propagation of holes in the domain then we simply let the // propagation handler handle it - propagation_handler.propagate_holes_in_domain(context, profile, task)?; + propagation_handler.propagate_holes_in_domain( + context, + profile, + task, + parameters.capacity.clone(), + )?; // Then we set the new profile index to maximum of the previous value of the new // profile index and the next profile index From ab03c1b7f160e3ab8534e94f97805d32e16a4d17 Mon Sep 17 00:00:00 2001 From: Imko Marijnissen Date: Wed, 17 Sep 2025 09:19:52 +0900 Subject: [PATCH 07/17] chore: only adding capacity predicate once --- .../cumulative/time_table/explanations/big_step.rs | 8 ++++++-- .../cumulative/time_table/explanations/naive.rs | 8 ++++++-- .../cumulative/time_table/explanations/pointwise.rs | 8 ++++++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/big_step.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/big_step.rs index a3e1a0cc1..475bb7cdd 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/big_step.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/big_step.rs @@ -40,9 +40,11 @@ pub(crate) fn create_big_step_propagation_explanation< profile_task.resource_usage >= context.lower_bound(&profile_task.resource_usage) ), - predicate!(capacity <= context.upper_bound(&capacity)), ] }) + .chain(std::iter::once(predicate!( + capacity <= context.upper_bound(&capacity) + ))) .filter(|&predicate| predicate != Predicate::trivially_true()) .collect() } @@ -79,9 +81,11 @@ pub(crate) fn create_big_step_conflict_explanation< profile_task.resource_usage >= context.lower_bound(&profile_task.resource_usage) ), - predicate!(capacity <= context.upper_bound(&capacity)), ] }) + .chain(std::iter::once(predicate!( + capacity <= context.upper_bound(&capacity) + ))) .filter(|&predicate| predicate != Predicate::trivially_true()) .collect() } diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/naive.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/naive.rs index 115f10995..dabd5388c 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/naive.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/naive.rs @@ -42,9 +42,11 @@ pub(crate) fn create_naive_propagation_explanation< profile_task.resource_usage >= context.lower_bound(&profile_task.resource_usage) ), - predicate!(capacity <= context.upper_bound(&capacity)), ] }) + .chain(std::iter::once(predicate!( + capacity <= context.upper_bound(&capacity) + ))) .filter(|&predicate| predicate != Predicate::trivially_true()) .collect() } @@ -83,9 +85,11 @@ pub(crate) fn create_naive_conflict_explanation< profile_task.resource_usage >= context.lower_bound(&profile_task.resource_usage) ), - predicate!(capacity <= context.upper_bound(&capacity)), ] }) + .chain(std::iter::once(predicate!( + capacity <= context.upper_bound(&capacity) + ))) .filter(|&predicate| predicate != Predicate::trivially_true()) .collect() } diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/pointwise.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/pointwise.rs index 3681ff702..a64c803d7 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/pointwise.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/pointwise.rs @@ -282,9 +282,11 @@ pub(crate) fn create_pointwise_propagation_explanation< profile_task.resource_usage >= context.lower_bound(&profile_task.resource_usage) ), - predicate!(capacity <= context.upper_bound(&capacity)), ] }) + .chain(std::iter::once(predicate!( + capacity <= context.upper_bound(&capacity) + ))) .filter(|&predicate| predicate != Predicate::trivially_true()) .collect() } @@ -326,9 +328,11 @@ pub(crate) fn create_pointwise_conflict_explanation< profile_task.resource_usage >= context.lower_bound(&profile_task.resource_usage) ), - predicate!(capacity <= context.upper_bound(&capacity)), ] }) + .chain(std::iter::once(predicate!( + capacity <= context.upper_bound(&capacity) + ))) .filter(|&predicate| predicate != Predicate::trivially_true()) .collect() } From 1e071bf1dfea6c177d10d813a3143d5b4f9f1b54 Mon Sep 17 00:00:00 2001 From: Imko Marijnissen Date: Wed, 17 Sep 2025 10:37:39 +0900 Subject: [PATCH 08/17] refactor: remove capacity bound from profile height explanation --- .../cumulative/time_table/explanations/big_step.rs | 5 ----- .../cumulative/time_table/explanations/naive.rs | 5 ----- .../cumulative/time_table/explanations/pointwise.rs | 5 ----- .../synchronisation.rs | 1 - .../time_table_over_interval_incremental.rs | 2 -- .../synchronisation.rs | 1 - .../time_table_per_point_incremental.rs | 2 -- .../cumulative/time_table/propagation_handler.rs | 11 ++++------- .../cumulative/time_table/time_table_over_interval.rs | 1 - .../cumulative/time_table/time_table_per_point.rs | 1 - 10 files changed, 4 insertions(+), 30 deletions(-) diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/big_step.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/big_step.rs index 475bb7cdd..57b5f44a3 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/big_step.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/big_step.rs @@ -55,11 +55,9 @@ pub(crate) fn create_big_step_conflict_explanation< Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, - CVar: IntegerVariable + 'static, >( context: PropagationContext, conflict_profile: &ResourceProfile, - capacity: CVar, ) -> PropositionalConjunction { conflict_profile .profile_tasks @@ -83,9 +81,6 @@ pub(crate) fn create_big_step_conflict_explanation< ), ] }) - .chain(std::iter::once(predicate!( - capacity <= context.upper_bound(&capacity) - ))) .filter(|&predicate| predicate != Predicate::trivially_true()) .collect() } diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/naive.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/naive.rs index dabd5388c..dbbde68c8 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/naive.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/naive.rs @@ -57,12 +57,10 @@ pub(crate) fn create_naive_conflict_explanation< Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, - CVar: IntegerVariable + 'static, Context: ReadDomains + Copy, >( conflict_profile: &ResourceProfile, context: Context, - capacity: CVar, ) -> PropositionalConjunction { conflict_profile .profile_tasks @@ -87,9 +85,6 @@ pub(crate) fn create_naive_conflict_explanation< ), ] }) - .chain(std::iter::once(predicate!( - capacity <= context.upper_bound(&capacity) - ))) .filter(|&predicate| predicate != Predicate::trivially_true()) .collect() } diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/pointwise.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/pointwise.rs index a64c803d7..5291be742 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/pointwise.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/pointwise.rs @@ -297,11 +297,9 @@ pub(crate) fn create_pointwise_conflict_explanation< Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, - CVar: IntegerVariable + 'static, >( context: PropagationContext, conflict_profile: &ResourceProfile, - capacity: CVar, ) -> PropositionalConjunction { // As stated in improving scheduling by learning, we choose the middle point; this // could potentially be improved @@ -330,9 +328,6 @@ pub(crate) fn create_pointwise_conflict_explanation< ), ] }) - .chain(std::iter::once(predicate!( - capacity <= context.upper_bound(&capacity) - ))) .filter(|&predicate| predicate != Predicate::trivially_true()) .collect() } diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/synchronisation.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/synchronisation.rs index 183db10b9..c5bbbb4a9 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/synchronisation.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/synchronisation.rs @@ -132,7 +132,6 @@ pub(crate) fn create_synchronised_conflict_explanation< height: resource_usage, }, parameters.options.explanation_type, - parameters.capacity.clone(), ) .into()) } diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/time_table_over_interval_incremental.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/time_table_over_interval_incremental.rs index efa4706c5..4cbca6d72 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/time_table_over_interval_incremental.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/time_table_over_interval_incremental.rs @@ -233,7 +233,6 @@ impl< self.inference_code, &conflict_tasks, self.parameters.options.explanation_type, - self.parameters.capacity.clone(), ) .into())); } @@ -408,7 +407,6 @@ impl< self.inference_code, conflicting_profile, self.parameters.options.explanation_type, - self.parameters.capacity.clone(), ) .into()); } diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/synchronisation.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/synchronisation.rs index 25d56e1e6..c0ab164d9 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/synchronisation.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/synchronisation.rs @@ -136,7 +136,6 @@ pub(crate) fn create_synchronised_conflict_explanation< height: new_height, }, parameters.options.explanation_type, - parameters.capacity.clone(), ) .into()) } diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs index f218b4619..4f9436c4f 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs @@ -233,7 +233,6 @@ impl< self.inference_code, current_profile, self.parameters.options.explanation_type, - self.parameters.capacity.clone(), ) .into())); } @@ -413,7 +412,6 @@ impl< self.inference_code, conflicting_profile, self.parameters.options.explanation_type, - self.parameters.capacity.clone(), ) .into()); } diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs index 3b51f9731..271f48645 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs @@ -472,28 +472,26 @@ impl CumulativePropagationHandler { /// Creates an explanation of the conflict caused by `conflict_profile` based on the provided /// `explanation_type`. -pub(crate) fn create_explanation_profile_height( +pub(crate) fn create_explanation_profile_height( context: PropagationContext, inference_code: InferenceCode, conflict_profile: &ResourceProfile, explanation_type: CumulativeExplanationType, - capacity: CVar, ) -> PropagatorConflict where Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, RVar: IntegerVariable + 'static, - CVar: IntegerVariable + 'static, { let conjunction = match explanation_type { CumulativeExplanationType::Naive => { - create_naive_conflict_explanation(conflict_profile, context, capacity) + create_naive_conflict_explanation(conflict_profile, context) } CumulativeExplanationType::BigStep => { - create_big_step_conflict_explanation(context, conflict_profile, capacity) + create_big_step_conflict_explanation(context, conflict_profile) } CumulativeExplanationType::Pointwise => { - create_pointwise_conflict_explanation(context, conflict_profile, capacity) + create_pointwise_conflict_explanation(context, conflict_profile) } }; @@ -581,7 +579,6 @@ pub(crate) mod test_propagation_handler { self.propagation_handler.inference_code, &profile, self.propagation_handler.explanation_type, - 0, ); (reason.conjunction, y) diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_over_interval.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_over_interval.rs index 488ce258e..add5dacba 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_over_interval.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_over_interval.rs @@ -434,7 +434,6 @@ fn create_time_table_from_events< inference_code, &new_profile, parameters.options.explanation_type, - parameters.capacity.clone(), ) .conjunction, inference_code, diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_per_point.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_per_point.rs index 0c1097d4b..f083139a2 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_per_point.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_per_point.rs @@ -299,7 +299,6 @@ pub(crate) fn create_time_table_per_point_from_scratch< inference_code, current_profile, parameters.options.explanation_type, - parameters.capacity.clone(), ) .conjunction, inference_code, From 21a5e5fb7ba3b825223fdadf8fcda3ed3f0094da Mon Sep 17 00:00:00 2001 From: Imko Marijnissen Date: Wed, 17 Sep 2025 10:54:14 +0900 Subject: [PATCH 09/17] fix: adding lower-bound on resource usage and duration to explanation --- .../time_table/explanations/big_step.rs | 31 ++++++++++--- .../cumulative/time_table/explanations/mod.rs | 13 +++--- .../time_table/explanations/naive.rs | 26 +++++++++-- .../time_table/explanations/pointwise.rs | 46 +++++++++++++------ .../time_table/propagation_handler.rs | 4 +- 5 files changed, 88 insertions(+), 32 deletions(-) diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/big_step.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/big_step.rs index 57b5f44a3..904e2e0be 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/big_step.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/big_step.rs @@ -89,25 +89,44 @@ pub(crate) fn create_big_step_predicate_propagating_task_lower_bound_propagation context: PropagationContext, task: &Rc>, profile: &ResourceProfile, -) -> Predicate +) -> PropositionalConjunction where Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, { - predicate!( - task.start_variable >= profile.start + 1 - context.lower_bound(&task.processing_time) - ) + [ + predicate!( + task.start_variable >= profile.start + 1 - context.lower_bound(&task.processing_time) + ), + predicate!(task.processing_time >= context.lower_bound(&task.processing_time)), + predicate!(task.resource_usage >= context.lower_bound(&task.resource_usage)), + ] + .into_iter() + .filter(|&predicate| predicate != Predicate::trivially_true()) + .collect() } pub(crate) fn create_big_step_predicate_propagating_task_upper_bound_propagation( task: &Rc>, profile: &ResourceProfile, context: PropagationContext, -) -> Predicate +) -> PropositionalConjunction where Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, { - predicate!(task.start_variable <= max(context.upper_bound(&task.start_variable), profile.end)) + [ + predicate!( + task.start_variable <= max(context.upper_bound(&task.start_variable), profile.end) + ), + predicate!(task.processing_time >= context.lower_bound(&task.processing_time)), + predicate!(task.resource_usage >= context.lower_bound(&task.resource_usage)), + ] + .into_iter() + .filter(|&predicate| predicate != Predicate::trivially_true()) + .collect() } #[cfg(test)] diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/mod.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/mod.rs index 481710035..ae0755d36 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/mod.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/mod.rs @@ -12,7 +12,6 @@ use pointwise::create_pointwise_predicate_propagating_task_lower_bound_propagati use pointwise::create_pointwise_predicate_propagating_task_upper_bound_propagation; use crate::engine::propagation::PropagationContext; -use crate::predicates::Predicate; use crate::predicates::PropositionalConjunction; use crate::propagators::ResourceProfile; use crate::propagators::Task; @@ -79,7 +78,7 @@ pub(crate) fn create_predicate_propagating_task_lower_bound_propagation< task: &Rc>, profile: &ResourceProfile, time_point: Option, -) -> Predicate { +) -> PropositionalConjunction { match explanation_type { CumulativeExplanationType::Naive => { create_naive_predicate_propagating_task_lower_bound_propagation(context, task) @@ -110,7 +109,7 @@ pub(crate) fn add_propagating_task_predicate_lower_bound< profile: &ResourceProfile, time_point: Option, ) -> PropositionalConjunction { - explanation.add(create_predicate_propagating_task_lower_bound_propagation( + explanation.extend(create_predicate_propagating_task_lower_bound_propagation( explanation_type, context, task, @@ -131,7 +130,7 @@ pub(crate) fn create_predicate_propagating_task_upper_bound_propagation< task: &Rc>, profile: &ResourceProfile, time_point: Option, -) -> Predicate { +) -> PropositionalConjunction { match explanation_type { CumulativeExplanationType::Naive => { create_naive_predicate_propagating_task_upper_bound_propagation(context, task) @@ -142,7 +141,9 @@ pub(crate) fn create_predicate_propagating_task_upper_bound_propagation< ) } CumulativeExplanationType::Pointwise => { - create_pointwise_predicate_propagating_task_upper_bound_propagation(task, time_point) + create_pointwise_predicate_propagating_task_upper_bound_propagation( + context, task, time_point, + ) } } } @@ -160,7 +161,7 @@ pub(crate) fn add_propagating_task_predicate_upper_bound< profile: &ResourceProfile, time_point: Option, ) -> PropositionalConjunction { - explanation.add(create_predicate_propagating_task_upper_bound_propagation( + explanation.extend(create_predicate_propagating_task_upper_bound_propagation( explanation_type, context, task, diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/naive.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/naive.rs index dbbde68c8..a8e8b2468 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/naive.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/naive.rs @@ -92,21 +92,39 @@ pub(crate) fn create_naive_conflict_explanation< pub(crate) fn create_naive_predicate_propagating_task_lower_bound_propagation( context: PropagationContext, task: &Rc>, -) -> Predicate +) -> PropositionalConjunction where Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, { - predicate!(task.start_variable >= context.lower_bound(&task.start_variable)) + [ + predicate!(task.start_variable >= context.lower_bound(&task.start_variable)), + predicate!(task.processing_time >= context.lower_bound(&task.processing_time)), + predicate!(task.resource_usage >= context.lower_bound(&task.resource_usage)), + ] + .into_iter() + .filter(|&predicate| predicate != Predicate::trivially_true()) + .collect() } pub(crate) fn create_naive_predicate_propagating_task_upper_bound_propagation( context: PropagationContext, task: &Rc>, -) -> Predicate +) -> PropositionalConjunction where Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, { - predicate!(task.start_variable <= context.upper_bound(&task.start_variable)) + [ + predicate!(task.start_variable <= context.upper_bound(&task.start_variable)), + predicate!(task.processing_time >= context.lower_bound(&task.processing_time)), + predicate!(task.resource_usage >= context.lower_bound(&task.resource_usage)), + ] + .into_iter() + .filter(|&predicate| predicate != Predicate::trivially_true()) + .collect() } #[cfg(test)] diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/pointwise.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/pointwise.rs index 5291be742..a9ad621d5 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/pointwise.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/explanations/pointwise.rs @@ -336,32 +336,50 @@ pub(crate) fn create_pointwise_predicate_propagating_task_lower_bound_propagatio context: PropagationContext, task: &Rc>, time_point: Option, -) -> Predicate +) -> PropositionalConjunction where Var: IntegerVariable + 'static, PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, { - predicate!( - task.start_variable - >= time_point - .expect("Expected time-point to be provided to pointwise explanation creation") - + 1 - - context.lower_bound(&task.processing_time) - ) + [ + predicate!( + task.start_variable + >= time_point + .expect("Expected time-point to be provided to pointwise explanation creation") + + 1 + - context.lower_bound(&task.processing_time) + ), + predicate!(task.processing_time >= context.lower_bound(&task.processing_time)), + predicate!(task.resource_usage >= context.lower_bound(&task.resource_usage)), + ] + .into_iter() + .filter(|&predicate| predicate != Predicate::trivially_true()) + .collect() } pub(crate) fn create_pointwise_predicate_propagating_task_upper_bound_propagation( + context: PropagationContext, task: &Rc>, time_point: Option, -) -> Predicate +) -> PropositionalConjunction where Var: IntegerVariable + 'static, + PVar: IntegerVariable + 'static, + RVar: IntegerVariable + 'static, { - predicate!( - task.start_variable - <= time_point - .expect("Expected time-point to be provided to pointwise explanation creation") - ) + [ + predicate!( + task.start_variable + <= time_point + .expect("Expected time-point to be provided to pointwise explanation creation") + ), + predicate!(task.processing_time >= context.lower_bound(&task.processing_time)), + predicate!(task.resource_usage >= context.lower_bound(&task.resource_usage)), + ] + .into_iter() + .filter(|&predicate| predicate != Predicate::trivially_true()) + .collect() } #[cfg(test)] diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs index 271f48645..9bb6466af 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs @@ -251,7 +251,7 @@ impl CumulativePropagationHandler { pumpkin_assert_extreme!(check_explanation(&explanation, context.as_readonly())); let mut reason = (*explanation).clone(); - reason.add(lower_bound_predicate_propagating_task); + reason.extend(lower_bound_predicate_propagating_task); context.post( predicate![propagating_task.start_variable >= profile.end + 1], reason, @@ -308,7 +308,7 @@ impl CumulativePropagationHandler { pumpkin_assert_extreme!(check_explanation(&explanation, context.as_readonly())); let mut reason = (*explanation).clone(); - reason.add(upper_bound_predicate_propagating_task); + reason.extend(upper_bound_predicate_propagating_task); context.post( predicate![ propagating_task.start_variable From 4a9bcfe7e9414d29df409da48721f28641454103 Mon Sep 17 00:00:00 2001 From: Imko Marijnissen Date: Wed, 17 Sep 2025 13:57:40 +0900 Subject: [PATCH 10/17] fix: only fixing tasks when the duration and resource usage are also fixed --- .../time_table_over_interval_incremental.rs | 10 +++++----- .../time_table_per_point_incremental.rs | 12 +++++------ .../time_table/time_table_over_interval.rs | 11 +++++----- .../cumulative/time_table/time_table_util.rs | 20 +++++++++++++++---- .../utils/structs/updatable_structures.rs | 5 ++++- 5 files changed, 36 insertions(+), 22 deletions(-) diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/time_table_over_interval_incremental.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/time_table_over_interval_incremental.rs index 4cbca6d72..93c12db3d 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/time_table_over_interval_incremental.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/over_interval_incremental_propagator/time_table_over_interval_incremental.rs @@ -487,7 +487,7 @@ impl< &mut self, context: PropagationContextWithTrailedValues, local_id: LocalId, - event: OpaqueDomainEvent, + _event: OpaqueDomainEvent, ) -> EnqueueDecision { let updated_task = Rc::clone(&self.parameters.tasks[local_id.unpack() as usize]); // Note that we do not take into account the fact that the time-table could be outdated @@ -515,10 +515,10 @@ impl< &updated_task, ); - if matches!( - updated_task.start_variable.unpack_event(event), - DomainEvent::Assign - ) { + if context.is_fixed(&updated_task.start_variable) + && context.is_fixed(&updated_task.processing_time) + && context.is_fixed(&updated_task.resource_usage) + { self.updatable_structures.fix_task(&updated_task) } diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs index 4f9436c4f..248799177 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs @@ -495,7 +495,7 @@ impl< &mut self, context: PropagationContextWithTrailedValues, local_id: LocalId, - event: OpaqueDomainEvent, + _event: OpaqueDomainEvent, ) -> EnqueueDecision { let updated_task = Rc::clone(&self.parameters.tasks[local_id.unpack() as usize]); // Note that we do not take into account the fact that the time-table could be outdated @@ -523,11 +523,11 @@ impl< &updated_task, ); - if matches!( - updated_task.start_variable.unpack_event(event), - DomainEvent::Assign - ) { - self.updatable_structures.fix_task(&updated_task); + if context.is_fixed(&updated_task.start_variable) + && context.is_fixed(&updated_task.processing_time) + && context.is_fixed(&updated_task.resource_usage) + { + self.updatable_structures.fix_task(&updated_task) } result.decision diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_over_interval.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_over_interval.rs index add5dacba..9003225b0 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_over_interval.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_over_interval.rs @@ -7,7 +7,6 @@ use crate::basic_types::Inconsistency; use crate::basic_types::PropagationStatusCP; use crate::basic_types::PropagatorConflict; use crate::conjunction; -use crate::engine::notifications::DomainEvent; use crate::engine::notifications::OpaqueDomainEvent; use crate::engine::propagation::constructor::PropagatorConstructor; use crate::engine::propagation::constructor::PropagatorConstructorContext; @@ -201,7 +200,7 @@ impl< &mut self, context: PropagationContextWithTrailedValues, local_id: LocalId, - event: OpaqueDomainEvent, + _event: OpaqueDomainEvent, ) -> EnqueueDecision { if local_id.unpack() as usize >= self.parameters.tasks.len() { // The upper-bound of the capacity has been updated; we should enqueue @@ -228,10 +227,10 @@ impl< &updated_task, ); - if matches!( - updated_task.start_variable.unpack_event(event), - DomainEvent::Assign - ) { + if context.is_fixed(&updated_task.start_variable) + && context.is_fixed(&updated_task.processing_time) + && context.is_fixed(&updated_task.resource_usage) + { self.updatable_structures.fix_task(&updated_task) } diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_util.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_util.rs index 404d5a1db..66dda630b 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_util.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/time_table_util.rs @@ -234,13 +234,19 @@ pub(crate) fn propagate_based_on_timetable< pumpkin_assert_extreme!( updatable_structures .get_unfixed_tasks() - .all(|unfixed_task| !context.is_fixed(&unfixed_task.start_variable)), + .all( + |unfixed_task| !context.is_fixed(&unfixed_task.start_variable) + || !context.is_fixed(&unfixed_task.processing_time) + || !context.is_fixed(&unfixed_task.resource_usage) + ), "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(|fixed_task| context.is_fixed(&fixed_task.start_variable) + && context.is_fixed(&fixed_task.processing_time) + && context.is_fixed(&fixed_task.resource_usage)), "All of the fixed tasks should be fixed at this point" ); @@ -301,7 +307,10 @@ fn propagate_single_profiles< 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) { + if context.is_fixed(&task.start_variable) + && context.is_fixed(&task.resource_usage) + && context.is_fixed(&task.processing_time) + { // The task is currently fixed after propagating // // Note that we fix this task temporarily and then wait for the notification to @@ -402,7 +411,10 @@ fn propagate_sequence_of_profiles< // Then we go over all the possible tasks for task in updatable_structures.get_unfixed_tasks() { - if context.is_fixed(&task.start_variable) { + if context.is_fixed(&task.start_variable) + && context.is_fixed(&task.resource_usage) + && context.is_fixed(&task.processing_time) + { // If the task is fixed then we are not able to propagate it further continue; } diff --git a/pumpkin-crates/core/src/propagators/cumulative/utils/structs/updatable_structures.rs b/pumpkin-crates/core/src/propagators/cumulative/utils/structs/updatable_structures.rs index 8d864a5ff..f3956a0bb 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/utils/structs/updatable_structures.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/utils/structs/updatable_structures.rs @@ -194,7 +194,10 @@ impl< context.lower_bound(&task.start_variable), context.upper_bound(&task.start_variable), )); - if context.is_fixed(&task.start_variable) { + if context.is_fixed(&task.start_variable) + && context.is_fixed(&task.processing_time) + && context.is_fixed(&task.resource_usage) + { self.fix_task(task); } } From 609d958c81ccc050c536c2107049c1aadd076540 Mon Sep 17 00:00:00 2001 From: Imko Marijnissen Date: Wed, 17 Sep 2025 14:13:36 +0900 Subject: [PATCH 11/17] fix: perform check explanation after completing --- .../cumulative/time_table/propagation_handler.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs index 9bb6466af..9bdae4535 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs @@ -248,10 +248,11 @@ impl CumulativePropagationHandler { profile, None, ); - pumpkin_assert_extreme!(check_explanation(&explanation, context.as_readonly())); let mut reason = (*explanation).clone(); reason.extend(lower_bound_predicate_propagating_task); + + pumpkin_assert_extreme!(check_explanation(&explanation, context.as_readonly())); context.post( predicate![propagating_task.start_variable >= profile.end + 1], reason, @@ -305,10 +306,12 @@ impl CumulativePropagationHandler { profile, None, ); - pumpkin_assert_extreme!(check_explanation(&explanation, context.as_readonly())); let mut reason = (*explanation).clone(); reason.extend(upper_bound_predicate_propagating_task); + + pumpkin_assert_extreme!(check_explanation(&explanation, context.as_readonly())); + context.post( predicate![ propagating_task.start_variable From 177dab732eee54bc2339d7b119901cf872eeac72 Mon Sep 17 00:00:00 2001 From: Imko Marijnissen Date: Wed, 17 Sep 2025 14:16:20 +0900 Subject: [PATCH 12/17] fix: also enqueue when variables are fixed --- .../core/src/propagators/cumulative/utils/util.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pumpkin-crates/core/src/propagators/cumulative/utils/util.rs b/pumpkin-crates/core/src/propagators/cumulative/utils/util.rs index 8fb1c0513..0fd22911f 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/utils/util.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/utils/util.rs @@ -80,12 +80,16 @@ pub(crate) fn register_tasks< ); context.register( task.processing_time.clone(), - DomainEvents::create_with_int_events(enum_set!(DomainEvent::LowerBound)), + DomainEvents::create_with_int_events(enum_set!( + DomainEvent::LowerBound | DomainEvent::Assign + )), task.id, ); context.register( task.resource_usage.clone(), - DomainEvents::create_with_int_events(enum_set!(DomainEvent::LowerBound)), + DomainEvents::create_with_int_events(enum_set!( + DomainEvent::LowerBound | DomainEvent::Assign + )), task.id, ); From d50122c8b62bbb2392e9bcd16b3645f9d8d3c510 Mon Sep 17 00:00:00 2001 From: Imko Marijnissen Date: Wed, 17 Sep 2025 14:32:15 +0900 Subject: [PATCH 13/17] fix: checking reason passed to context --- .../propagators/cumulative/time_table/propagation_handler.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs index 9bdae4535..294fe4d9a 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/propagation_handler.rs @@ -252,7 +252,7 @@ impl CumulativePropagationHandler { let mut reason = (*explanation).clone(); reason.extend(lower_bound_predicate_propagating_task); - pumpkin_assert_extreme!(check_explanation(&explanation, context.as_readonly())); + pumpkin_assert_extreme!(check_explanation(&reason, context.as_readonly())); context.post( predicate![propagating_task.start_variable >= profile.end + 1], reason, @@ -310,7 +310,7 @@ impl CumulativePropagationHandler { let mut reason = (*explanation).clone(); reason.extend(upper_bound_predicate_propagating_task); - pumpkin_assert_extreme!(check_explanation(&explanation, context.as_readonly())); + pumpkin_assert_extreme!(check_explanation(&reason, context.as_readonly())); context.post( predicate![ From eb73e2c91611d7cca2ee70fdaeed62d506e99842 Mon Sep 17 00:00:00 2001 From: Imko Marijnissen Date: Fri, 19 Sep 2025 15:17:57 +0900 Subject: [PATCH 14/17] docs: fixing doc tests --- .../core/src/constraints/cumulative.rs | 21 +++++++++-- .../core/src/propagators/cumulative/mod.rs | 37 +++++++++++++------ 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/pumpkin-crates/core/src/constraints/cumulative.rs b/pumpkin-crates/core/src/constraints/cumulative.rs index 6841f56c8..ec7a51b12 100644 --- a/pumpkin-crates/core/src/constraints/cumulative.rs +++ b/pumpkin-crates/core/src/constraints/cumulative.rs @@ -43,6 +43,7 @@ use crate::Solver; /// # use pumpkin_core::constraints; /// # use pumpkin_core::constraints::Constraint; /// # use crate::pumpkin_core::results::ProblemSolution; +/// # use pumpkin_core::constraint_arguments::ArgTask; /// let solver = Solver::default(); /// /// let mut solver = Solver::default(); @@ -60,9 +61,23 @@ use crate::Solver; /// /// solver /// .add_constraint(constraints::cumulative( -/// start_times.clone(), -/// durations.clone(), -/// resource_requirements.clone(), +/// [ +/// ArgTask { +/// start_time: start_0, +/// processing_time: 5, +/// resource_usage: 1, +/// }, +/// ArgTask { +/// start_time: start_1, +/// processing_time: 2, +/// resource_usage: 1, +/// }, +/// ArgTask { +/// start_time: start_2, +/// processing_time: 5, +/// resource_usage: 2, +/// }, +/// ], /// resource_capacity, /// constraint_tag, /// )) diff --git a/pumpkin-crates/core/src/propagators/cumulative/mod.rs b/pumpkin-crates/core/src/propagators/cumulative/mod.rs index db9e5d92f..f9e4c216a 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/mod.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/mod.rs @@ -43,26 +43,41 @@ //! # use pumpkin_core::results::SatisfactionResult; //! # use pumpkin_core::constraints; //! # use pumpkin_core::constraints::Constraint; -//! # use crate::pumpkin_core::results::ProblemSolution; +//! # use pumpkin_core::results::ProblemSolution; +//! # use pumpkin_core::constraint_arguments::ArgTask; //! let mut solver = Solver::default(); //! -//! let start_0 = solver.new_bounded_integer(0, 4); -//! let start_1 = solver.new_bounded_integer(0, 4); -//! let start_2 = solver.new_bounded_integer(0, 5); +//! let constraint_tag = solver.new_constraint_tag(); //! -//! let start_times = [start_0, start_1, start_2]; +//! let resource_capacity = 2; //! let durations = [5, 2, 5]; //! let resource_requirements = [1, 1, 2]; -//! let resource_capacity = 2; //! -//! let c1 = solver.new_constraint_tag(); +//! let start_0 = solver.new_bounded_integer(0, 4); +//! let start_1 = solver.new_bounded_integer(0, 4); +//! let start_2 = solver.new_bounded_integer(0, 5); +//! //! solver //! .add_constraint(constraints::cumulative( -//! start_times.clone(), -//! durations.clone(), -//! resource_requirements.clone(), +//! [ +//! ArgTask { +//! start_time: start_0, +//! processing_time: durations[0], +//! resource_usage: resource_requirements[0], +//! }, +//! ArgTask { +//! start_time: start_1, +//! processing_time: durations[1], +//! resource_usage: resource_requirements[1], +//! }, +//! ArgTask { +//! start_time: start_2, +//! processing_time: durations[2], +//! resource_usage: resource_requirements[2], +//! }, +//! ], //! resource_capacity, -//! c1, +//! constraint_tag, //! )) //! .post(); //! From ea63caa3897500ddc9a1bd31e6f67db24ac1ca3b Mon Sep 17 00:00:00 2001 From: Imko Marijnissen Date: Tue, 11 Nov 2025 10:22:40 +0100 Subject: [PATCH 15/17] fix: python interface only supports non-variable cumulative (for now) --- pumpkin-solver-py/src/constraints/globals.rs | 134 ++++++------------- pumpkin-solver-py/src/constraints/mod.rs | 1 + pumpkin-solver-py/tests/test_constraints.py | 77 +++++------ 3 files changed, 75 insertions(+), 137 deletions(-) diff --git a/pumpkin-solver-py/src/constraints/globals.rs b/pumpkin-solver-py/src/constraints/globals.rs index 0052adfc1..1b7d61664 100644 --- a/pumpkin-solver-py/src/constraints/globals.rs +++ b/pumpkin-solver-py/src/constraints/globals.rs @@ -1,6 +1,8 @@ use pumpkin_solver::constraint_arguments::ArgTask; use pumpkin_solver::constraints::Constraint; use pumpkin_solver::constraints::{self}; +use pumpkin_solver::variables::AffineView; +use pumpkin_solver::variables::DomainId; use pyo3::pyclass; use pyo3::pymethods; @@ -55,99 +57,6 @@ macro_rules! python_constraint { }; } -#[pyclass] -#[derive(Clone)] -pub struct Cumulative { - start_times: Vec, - durations: Vec, - resource_usages: Vec, - resource_capacity: IntExpression, - constraint_tag: Tag, -} - -#[pymethods] -impl Cumulative { - #[new] - pub fn new( - start_times: Vec, - durations: Vec, - resource_usages: Vec, - resource_capacity: IntExpression, - constraint_tag: Tag, - ) -> Self { - Self { - constraint_tag, - start_times, - durations, - resource_usages, - resource_capacity, - } - } -} - -impl Cumulative { - pub fn post( - self, - solver: &mut pumpkin_solver::Solver, - variable_map: &VariableMap, - ) -> Result<(), pumpkin_solver::ConstraintOperationError> { - let tasks = self - .start_times - .into_iter() - .zip(self.durations.iter()) - .zip(self.resource_usages.iter()) - .map(|((start_time, processing_time), resource_usage)| { - let start_time = start_time.to_solver_constraint_argument(variable_map); - let processing_time = processing_time.to_solver_constraint_argument(variable_map); - let resource_usage = resource_usage.to_solver_constraint_argument(variable_map); - ArgTask { - start_time, - processing_time, - resource_usage, - } - }) - .collect::>(); - constraints::cumulative( - tasks, - self.resource_capacity - .to_solver_constraint_argument(variable_map), - self.constraint_tag.0, - ) - .post(solver) - } - - pub fn implied_by( - self, - solver: &mut pumpkin_solver::Solver, - reification_literal: pumpkin_solver::variables::Literal, - variable_map: &VariableMap, - ) -> Result<(), pumpkin_solver::ConstraintOperationError> { - let tasks = self - .start_times - .into_iter() - .zip(self.durations.iter()) - .zip(self.resource_usages.iter()) - .map(|((start_time, processing_time), resource_usage)| { - let start_time = start_time.to_solver_constraint_argument(variable_map); - let processing_time = processing_time.to_solver_constraint_argument(variable_map); - let resource_usage = resource_usage.to_solver_constraint_argument(variable_map); - ArgTask { - start_time, - processing_time, - resource_usage, - } - }) - .collect::>(); - constraints::cumulative( - tasks, - self.resource_capacity - .to_solver_constraint_argument(variable_map), - self.constraint_tag.0, - ) - .implied_by(solver, reification_literal) - } -} - python_constraint! { Absolute: absolute { signed: IntExpression, @@ -281,3 +190,42 @@ python_constraint! { table: Vec>, } } + +python_constraint! { + Cumulative: cumulative { + tasks: Vec, + capacity: i32 + } +} + +#[pyclass(eq, hash, frozen)] +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct Task { + start_time: IntExpression, + processing_time: i32, + resource_usage: i32, +} + +#[pymethods] +impl Task { + #[new] + fn new(start_time: IntExpression, processing_time: i32, resource_usage: i32) -> Self { + Self { + start_time, + processing_time, + resource_usage, + } + } +} + +impl PythonConstraintArg for Task { + type Output = ArgTask, i32, i32>; + + fn to_solver_constraint_argument(self, variable_map: &VariableMap) -> Self::Output { + ArgTask { + start_time: self.start_time.to_solver_constraint_argument(variable_map), + processing_time: self.processing_time, + resource_usage: self.resource_usage, + } + } +} diff --git a/pumpkin-solver-py/src/constraints/mod.rs b/pumpkin-solver-py/src/constraints/mod.rs index ec8080366..bd972a3cc 100644 --- a/pumpkin-solver-py/src/constraints/mod.rs +++ b/pumpkin-solver-py/src/constraints/mod.rs @@ -36,6 +36,7 @@ macro_rules! declare_constraints { pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { $(m.add_class::<$constraint>()?;)+ + m.add_class::()?; Ok(()) } }; diff --git a/pumpkin-solver-py/tests/test_constraints.py b/pumpkin-solver-py/tests/test_constraints.py index 0901863a5..cfcf972f7 100644 --- a/pumpkin-solver-py/tests/test_constraints.py +++ b/pumpkin-solver-py/tests/test_constraints.py @@ -1,15 +1,14 @@ -""" -Generate constraints and expressions based on the grammar supported by the API +"""Generate constraints and expressions based on the grammar supported by the API. -Generates linear constraints, special operators and global constraints. -Whenever possible, the script also generates 'boolean as integer' versions of the arguments +Generates linear constraints, special operators and global constraints. Whenever possible, the script also generates +'boolean as integer' versions of the arguments """ from random import randint +import pumpkin_solver import pytest from pumpkin_solver import constraints -import pumpkin_solver # generate all linear sum-expressions @@ -28,10 +27,7 @@ def generate_linear(): for i in range(3) ] else: - args = [ - model.new_integer_variable(-3, 5, name=f"x[{i}]") - for i in range(3) - ] + args = [model.new_integer_variable(-3, 5, name=f"x[{i}]") for i in range(3)] if scaled: # do scaling (0, -2, 4,...) args = [ a.scaled(-2 * i + 1) for i, a in enumerate(args) @@ -43,9 +39,7 @@ def generate_linear(): if comp == "!=": cons = constraints.NotEquals(args, rhs, model.new_constraint_tag()) if comp == "<=": - cons = constraints.LessThanOrEquals( - args, rhs, model.new_constraint_tag() - ) + cons = constraints.LessThanOrEquals(args, rhs, model.new_constraint_tag()) yield model, cons, comp, scaled, bool @@ -66,10 +60,7 @@ def generate_operators(): for i in range(3) ] else: - args = [ - model.new_integer_variable(-3, 5, name=f"x[{i}]") - for i in range(3) - ] + args = [model.new_integer_variable(-3, 5, name=f"x[{i}]") for i in range(3)] if scaled: # do scaling (0, -2, 4,...) args = [ a.scaled(-2 * i + 1) for i, a in enumerate(args) @@ -78,26 +69,18 @@ def generate_operators(): rhs = model.new_integer_variable(-3, 5, name="rhs") if name == "div": denom = model.new_integer_variable(1, 3, name="denom") - cons = constraints.Division( - args[0], denom, rhs, model.new_constraint_tag() - ) + cons = constraints.Division(args[0], denom, rhs, model.new_constraint_tag()) if name == "mul": cons = constraints.Times(*args[:2], rhs, model.new_constraint_tag()) if name == "abs": - cons = constraints.Absolute( - args[0], rhs, model.new_constraint_tag() - ) + cons = constraints.Absolute(args[0], rhs, model.new_constraint_tag()) if name == "min": cons = constraints.Minimum(args, rhs, model.new_constraint_tag()) if name == "max": cons = constraints.Maximum(args, rhs, model.new_constraint_tag()) if name == "element": - idx = model.new_integer_variable( - -1, 5, name="idx" - ) # sneaky, idx can be out of bounds - cons = constraints.Element( - idx, args, rhs, model.new_constraint_tag() - ) + idx = model.new_integer_variable(-1, 5, name="idx") # sneaky, idx can be out of bounds + cons = constraints.Element(idx, args, rhs, model.new_constraint_tag()) yield model, cons, name, scaled, bool @@ -116,9 +99,7 @@ def generate_alldiff(): for i in range(3) ] else: - args = [ - model.new_integer_variable(-3, 5, name=f"x[{i}]") for i in range(3) - ] + args = [model.new_integer_variable(-3, 5, name=f"x[{i}]") for i in range(3)] if scaled or bool: # do scaling (0, -2, 4,...) args = [ a.scaled(-2 * i + 1) for i, a in enumerate(args) @@ -154,17 +135,31 @@ def generate_cumulative(): capacity = 4 model = pumpkin_solver.Model() - start = [model.new_integer_variable(-3, 5, name=f"x[{i}]") for i in range(3)] + start = [model.new_integer_variable(0, 5, name=f"x[{i}]") for i in range(3)] cons = constraints.Cumulative( - start, duration, demand, capacity, model.new_constraint_tag() + list( + map( + lambda task: constraints.Task(*task), + zip(start, duration, demand), + ) + ), + capacity, + model.new_constraint_tag(), ) yield model, cons, "cumulative", False, False model = pumpkin_solver.Model() - start = [model.new_integer_variable(-3, 5, name=f"x[{i}]") for i in range(3)] + start = [model.new_integer_variable(0, 5, name=f"x[{i}]") for i in range(3)] start = [a.scaled(-2 * i) for i, a in enumerate(start)] cons = constraints.Cumulative( - start, duration, demand, capacity, model.new_constraint_tag() + list( + map( + lambda task: constraints.Task(*task), + zip(start, duration, demand), + ) + ), + capacity, + model.new_constraint_tag(), ) yield model, cons, "cumulative", True, False @@ -177,17 +172,13 @@ def generate_globals(): def label(model, cons, name, scaled, bool): - return " ".join( - ["Scaled" if scaled else "Unscaled", "Boolean" if bool else "Integer", name] - ) + return " ".join(["Scaled" if scaled else "Unscaled", "Boolean" if bool else "Integer", name]) LINEAR = list(generate_operators()) -@pytest.mark.parametrize( - ("model", "cons", "name", "scaled", "bool"), LINEAR, ids=[label(*a) for a in LINEAR] -) +@pytest.mark.parametrize(("model", "cons", "name", "scaled", "bool"), LINEAR, ids=[label(*a) for a in LINEAR]) def test_linear(model, cons, name, scaled, bool): model.add_constraint(cons) res = model.satisfy() @@ -222,9 +213,7 @@ def test_global(model, cons, name, scaled, bool): assert isinstance(res, pumpkin_solver.SatisfactionResult.Satisfiable) -ALL_EXPR = ( - list(generate_operators()) + list(generate_linear()) + list(generate_globals()) -) +ALL_EXPR = list(generate_operators()) + list(generate_linear()) + list(generate_globals()) @pytest.mark.parametrize( From b382c2a09c3b4718987877413e76b669da7bb5c8 Mon Sep 17 00:00:00 2001 From: Imko Marijnissen Date: Wed, 12 Nov 2025 09:10:43 +0100 Subject: [PATCH 16/17] fix: empty domain reasons in incremental test cases per point --- .../core/src/engine/cp/test_solver.rs | 21 +++++ .../time_table_per_point_incremental.rs | 82 +++++++++---------- 2 files changed, 61 insertions(+), 42 deletions(-) diff --git a/pumpkin-crates/core/src/engine/cp/test_solver.rs b/pumpkin-crates/core/src/engine/cp/test_solver.rs index 92053c1ee..e7e8610f1 100644 --- a/pumpkin-crates/core/src/engine/cp/test_solver.rs +++ b/pumpkin-crates/core/src/engine/cp/test_solver.rs @@ -318,6 +318,27 @@ impl TestSolver { ); } + pub(crate) fn get_reason_empty_domain(&mut self) -> Vec { + let (entry_reason, _) = self + .assignments + .get_last_entry_on_trail() + .reason + .expect("Cannot cause an empty domain using a decision."); + let mut reason_scratch = vec![]; + let _ = self.reason_store.get_or_compute( + entry_reason, + ExplanationContext::without_working_nogood( + &self.assignments, + self.assignments.num_trail_entries() - 1, + &mut self.notification_engine, + ), + &mut self.propagator_store, + &mut reason_scratch, + ); + + reason_scratch + } + pub(crate) fn get_reason_int(&mut self, predicate: Predicate) -> PropositionalConjunction { let reason_ref = self .assignments diff --git a/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs b/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs index b7c26ef5b..15aa8f492 100644 --- a/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs +++ b/pumpkin-crates/core/src/propagators/cumulative/time_table/per_point_incremental_propagator/time_table_per_point_incremental.rs @@ -1417,21 +1417,19 @@ mod tests { let _ = solver.increase_lower_bound_and_notify(propagator, 2, s3, 7); let _ = solver.increase_lower_bound_and_notify(propagator, 1, s2, 7); let result = solver.propagate(propagator); - assert!( - { - if let Err(Inconsistency::Conflict(conflict)) = &result { - if let Err(Inconsistency::Conflict(explanation_scratch)) = &result_scratch { - conflict.conjunction.iter().collect::>() - == explanation_scratch.conjunction.iter().collect::>() - } else { - false - } - } else { - false - } - }, - "The results are different than expected - Expected: {result_scratch:?} but was: {result:?}" - ); + if let Err(Inconsistency::Conflict(conflict)) = &result + && let Err(Inconsistency::EmptyDomain) = &result_scratch + { + let reason_scratch = solver_scratch.get_reason_empty_domain(); + assert_eq!( + conflict.conjunction.iter().copied().collect::>(), + reason_scratch + ) + } else { + panic!( + "The results are different than expected - Expected: {result_scratch:?} but was: {result:?}" + ) + } } #[test] @@ -1518,21 +1516,21 @@ mod tests { let _ = solver.increase_lower_bound_and_notify(propagator, 2, s3, 7); let _ = solver.increase_lower_bound_and_notify(propagator, 1, s2, 7); let result = solver.propagate(propagator); - assert!(result.is_err()); let result_scratch = solver_scratch.propagate(propagator_scratch); - assert!(result_scratch.is_err()); - assert!({ - if let Err(Inconsistency::Conflict(explanation)) = &result { - if let Err(Inconsistency::Conflict(explanation_scratch)) = &result_scratch { - explanation.conjunction.iter().collect::>() - == explanation_scratch.conjunction.iter().collect::>() - } else { - false - } - } else { - false - } - }); + + if let Err(Inconsistency::Conflict(explanation)) = &result + && let Err(Inconsistency::EmptyDomain) = &result_scratch + { + let reason_scratch = solver_scratch.get_reason_empty_domain(); + assert_eq!( + explanation.conjunction.iter().copied().collect::>(), + reason_scratch + ) + } else { + panic!( + "Expected {result_scratch:?} to be an empty domain and {result:?} to be a conflict" + ) + } } #[test] @@ -1575,6 +1573,7 @@ mod tests { solver_scratch.increase_lower_bound_and_notify(propagator_scratch, 2, s3_scratch, 7); let _ = solver_scratch.increase_lower_bound_and_notify(propagator_scratch, 1, s2_scratch, 7); + let result_scratch = solver_scratch.propagate(propagator_scratch); let mut solver = TestSolver::default(); let s1 = solver.new_variable(5, 5); @@ -1619,19 +1618,18 @@ mod tests { let _ = solver.increase_lower_bound_and_notify(propagator, 2, s3, 7); let _ = solver.increase_lower_bound_and_notify(propagator, 1, s2, 7); let result = solver.propagate(propagator); - let result_scratch = solver_scratch.propagate(propagator_scratch); - assert!({ - if let Err(Inconsistency::Conflict(explanation)) = &result { - if let Err(Inconsistency::Conflict(explanation_scratch)) = &result_scratch { - explanation.conjunction.iter().collect::>() - != explanation_scratch.conjunction.iter().collect::>() - } else { - false - } - } else { - false - } - }); + + if let Err(Inconsistency::Conflict(explanation)) = &result + && let Err(Inconsistency::EmptyDomain) = &result_scratch + { + let reason_scratch = solver_scratch.get_reason_empty_domain(); + assert_ne!( + explanation.conjunction.iter().copied().collect::>(), + reason_scratch + ) + } else { + panic!("{result:?} is not an error or {result_scratch:?} is not an error") + } } #[test] From b3f2f0aae948f858095adad50636495490b4b704 Mon Sep 17 00:00:00 2001 From: Imko Marijnissen Date: Wed, 10 Dec 2025 13:19:49 +0100 Subject: [PATCH 17/17] fix: getting reason for empty domain in test solver --- pumpkin-crates/core/src/engine/cp/test_solver.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pumpkin-crates/core/src/engine/cp/test_solver.rs b/pumpkin-crates/core/src/engine/cp/test_solver.rs index e7e8610f1..9bd21c2a7 100644 --- a/pumpkin-crates/core/src/engine/cp/test_solver.rs +++ b/pumpkin-crates/core/src/engine/cp/test_solver.rs @@ -319,11 +319,7 @@ impl TestSolver { } pub(crate) fn get_reason_empty_domain(&mut self) -> Vec { - let (entry_reason, _) = self - .assignments - .get_last_entry_on_trail() - .reason - .expect("Cannot cause an empty domain using a decision."); + let entry_reason = self.assignments.remove_last_trail_element().1; let mut reason_scratch = vec![]; let _ = self.reason_store.get_or_compute( entry_reason,