Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions vrp-cli/src/extensions/generate/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ mod plan_test;
use super::get_random_item;
use vrp_core::prelude::{Float, GenericError};
use vrp_core::utils::{DefaultRandom, Random};
use vrp_pragmatic::format::Location;
use vrp_pragmatic::format::problem::{Job, JobPlace, JobTask, Plan, Problem};
use vrp_pragmatic::format::Location;

/// Generates a new plan for given problem with amount of jobs specified by`jobs_size` and
/// bounding box of size `area_size` (half size in meters). When not specified, jobs bounding
Expand Down Expand Up @@ -73,7 +73,7 @@ pub(crate) fn generate_plan(
})
.collect();

Ok(Plan { jobs, relations: None, clustering: None })
Ok(Plan { jobs, relations: None, clustering: None, strict_departure: None })
}

type LocationFn = Box<dyn Fn(&DefaultRandom) -> Location>;
Expand Down
10 changes: 7 additions & 3 deletions vrp-cli/src/extensions/import/csv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,11 @@ mod actual {

let get_tasks = |jobs: &Vec<&CsvJob>, filter: Box<dyn Fn(&CsvJob) -> bool>| {
let tasks = jobs.iter().filter(|j| (filter)(j)).map(|job| get_task(job)).collect::<Vec<_>>();
if tasks.is_empty() { None } else { Some(tasks) }
if tasks.is_empty() {
None
} else {
Some(tasks)
}
};

let jobs = read_csv_entries::<CsvJob, _>(reader)?
Expand Down Expand Up @@ -155,7 +159,7 @@ mod actual {
let matrix_profile_names = vehicles.iter().map(|v| v.profile.matrix.clone()).collect::<HashSet<_>>();

Ok(Problem {
plan: Plan { jobs, relations: None, clustering: None },
plan: Plan { jobs, relations: None, clustering: None, strict_departure: None },
fleet: Fleet {
vehicles,
profiles: matrix_profile_names.into_iter().map(|name| MatrixProfile { name, speed: None }).collect(),
Expand All @@ -169,8 +173,8 @@ mod actual {
#[cfg(not(feature = "csv-format"))]
mod actual {
use std::io::{BufReader, Read};
use vrp_pragmatic::format::FormatError;
use vrp_pragmatic::format::problem::Problem;
use vrp_pragmatic::format::FormatError;

/// A stub method for reading problem from csv format.
pub fn read_csv_problem<R1: Read, R2: Read>(
Expand Down
9 changes: 6 additions & 3 deletions vrp-core/src/construction/features/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub(crate) use self::capacity::MaxVehicleLoadTourState;
pub use self::capacity::{CapacityFeatureBuilder, JobDemandDimension, VehicleCapacityDimension};

mod compatibility;
pub use self::compatibility::{JobCompatibilityDimension, create_compatibility_feature};
pub use self::compatibility::{create_compatibility_feature, JobCompatibilityDimension};

mod fast_service;
pub use self::fast_service::FastServiceFeatureBuilder;
Expand All @@ -24,7 +24,7 @@ mod fleet_usage;
pub use self::fleet_usage::*;

mod groups;
pub use self::groups::{JobGroupDimension, create_group_feature};
pub use self::groups::{create_group_feature, JobGroupDimension};

mod hierarchical_areas;
pub use self::hierarchical_areas::*;
Expand All @@ -48,7 +48,7 @@ mod reloads;
pub use self::reloads::{ReloadFeatureFactory, ReloadIntervalsTourState, SharedResource, SharedResourceId};

mod skills;
pub use self::skills::{JobSkills, JobSkillsDimension, VehicleSkillsDimension, create_skills_feature};
pub use self::skills::{create_skills_feature, JobSkills, JobSkillsDimension, VehicleSkillsDimension};

mod total_value;
pub use self::total_value::*;
Expand All @@ -70,3 +70,6 @@ pub use self::work_balance::{
create_activity_balanced_feature, create_distance_balanced_feature, create_duration_balanced_feature,
create_max_load_balanced_feature,
};

mod strict_departure;
pub use self::strict_departure::create_strict_departure_feature;
47 changes: 47 additions & 0 deletions vrp-core/src/construction/features/strict_departure.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//! This module provides functionailty to reject plans where
//! departures leave after a place's available times, instead
//! of merely having the restriction apply for arrival times
use std::sync::Arc;

use rosomaxa::utils::GenericError;

use crate::{
models::{ConstraintViolation, Feature, FeatureBuilder, FeatureConstraint, ViolationCode},
prelude::ActivityCost,
};

struct StrictDepartureConstraint {
time_constraint_code: ViolationCode,
activity: Arc<dyn ActivityCost>,
}

impl FeatureConstraint for StrictDepartureConstraint {
fn evaluate(&self, move_ctx: &crate::prelude::MoveContext<'_>) -> Option<crate::prelude::ConstraintViolation> {
match move_ctx {
crate::prelude::MoveContext::Activity { activity_ctx, route_ctx, .. } => {
let activity_departure = self.activity.estimate_departure(
route_ctx.route(),
activity_ctx.target,
activity_ctx.target.schedule.arrival,
);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the core logic (we can't use self.activity.schedule.departure directly because that departure might be dynamically updated through break's activity cost...)

let place_closing = activity_ctx.target.place.time.end;
if activity_departure > place_closing {
ConstraintViolation::fail(self.time_constraint_code)
} else {
None
}
}
_ => None,
}
}
}
pub fn create_strict_departure_feature(
name: &str,
activity: Arc<dyn ActivityCost>,
time_constraint_code: ViolationCode,
) -> Result<Feature, GenericError> {
FeatureBuilder::default()
.with_name(name)
.with_constraint(StrictDepartureConstraint { activity, time_constraint_code })
.build()
}
7 changes: 7 additions & 0 deletions vrp-pragmatic/src/format/problem/goal_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ pub(super) fn create_goal_context(
features.push(create_reachable_feature("reachable", blocks.transport.clone(), REACHABLE_CONSTRAINT_CODE)?)
}

if props.has_strict_departure {
features.push(create_strict_departure_feature(
"strict_departure",
blocks.activity.clone(),
TIME_CONSTRAINT_CODE,
)?)
}
features.push(get_capacity_feature("capacity", api_problem, blocks, props)?);

if props.has_tour_travel_limits {
Expand Down
9 changes: 7 additions & 2 deletions vrp-pragmatic/src/format/problem/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use super::*;
use crate::parse_time;
use std::io::{BufReader, Read};
use std::sync::Arc;
use vrp_core::models::Lock;
use vrp_core::models::common::TimeWindow;
use vrp_core::models::Lock;
use vrp_core::prelude::{ActivityCost, Fleet as CoreFleet, Jobs as CoreJobs, TransportCost};
use vrp_core::utils::*;

Expand Down Expand Up @@ -91,7 +91,11 @@ impl PragmaticProblem for ApiProblem {

impl PragmaticProblem for (ApiProblem, Option<Vec<Matrix>>) {
fn read_pragmatic(self) -> Result<CoreProblem, MultiFormatError> {
if let Some(matrices) = self.1 { (self.0, matrices).read_pragmatic() } else { self.0.read_pragmatic() }
if let Some(matrices) = self.1 {
(self.0, matrices).read_pragmatic()
} else {
self.0.read_pragmatic()
}
}
}

Expand All @@ -109,6 +113,7 @@ struct ProblemProperties {
has_compatibility: bool,
has_tour_size_limits: bool,
has_tour_travel_limits: bool,
has_strict_departure: bool,
}

/// Keeps track of materialized problem building blocks.
Expand Down
5 changes: 5 additions & 0 deletions vrp-pragmatic/src/format/problem/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ pub struct VicinityFilteringPolicy {

/// A plan specifies work which has to be done.
#[derive(Clone, Deserialize, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Plan {
/// List of jobs.
pub jobs: Vec<Job>,
Expand All @@ -225,6 +226,10 @@ pub struct Plan {
/// Specifies clustering parameters.
#[serde(skip_serializing_if = "Option::is_none")]
pub clustering: Option<Clustering>,

/// whether to use strict departure logic
#[serde(skip_serializing_if = "Option::is_none")]
pub strict_departure: Option<bool>,
}

// endregion
Expand Down
11 changes: 8 additions & 3 deletions vrp-pragmatic/src/format/problem/problem_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ use crate::format::problem::goal_reader::create_goal_context;
use crate::format::problem::job_reader::{read_jobs_with_extra_locks, read_locks};
use crate::format::{FormatError, JobIndex};
use crate::validation::ValidationContext;
use crate::{CoordIndex, parse_time};
use crate::{parse_time, CoordIndex};
use vrp_core::construction::enablers::*;
use vrp_core::models::Extras;
use vrp_core::models::common::{TimeOffset, TimeSpan, TimeWindow};
use vrp_core::models::Extras;
use vrp_core::solver::processing::{ClusterConfigExtraProperty, ReservedTimesExtraProperty};

pub(super) fn map_to_problem_with_approx(problem: ApiProblem) -> Result<CoreProblem, MultiFormatError> {
Expand Down Expand Up @@ -104,7 +104,11 @@ fn read_reserved_times_index(api_problem: &ApiProblem, fleet: &CoreFleet) -> Res
})
.collect::<Vec<_>>();

if times.is_empty() { None } else { Some((actor.clone(), times)) }
if times.is_empty() {
None
} else {
Some((actor.clone(), times))
}
})
.collect()
}
Expand Down Expand Up @@ -169,6 +173,7 @@ fn get_problem_properties(api_problem: &ApiProblem, matrices: &[Matrix]) -> Prob
has_compatibility,
has_tour_size_limits,
has_tour_travel_limits,
has_strict_departure: api_problem.plan.strict_departure == Some(true),
}
}

Expand Down