Skip to content

Commit

Permalink
Apply some performance optimizations
Browse files Browse the repository at this point in the history
  • Loading branch information
reinterpretcat committed Sep 22, 2024
1 parent 6a314a5 commit ca3d032
Show file tree
Hide file tree
Showing 17 changed files with 139 additions and 86 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ are already published. So, I stick to it for now.

* update rust version
* apply some minor code refactorings
* apply some performance optimizations


## [1.24.0] 2024-07-13
Expand Down
11 changes: 10 additions & 1 deletion vrp-core/src/models/domain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ pub struct ProblemBuilder {
activity: Option<Arc<dyn ActivityCost>>,
transport: Option<Arc<dyn TransportCost>>,
extras: Option<Arc<Extras>>,
logger: Option<InfoLogger>,
}

impl ProblemBuilder {
Expand Down Expand Up @@ -204,6 +205,12 @@ impl ProblemBuilder {
self
}

/// Adds a logger to the problem definition.
pub fn with_logger(mut self, logger: InfoLogger) -> Self {
self.logger = Some(logger);
self
}

/// Builds a problem definition.
/// Returns [Err] in case of an invalid configuration.
pub fn build(mut self) -> GenericResult<Problem> {
Expand Down Expand Up @@ -233,8 +240,10 @@ impl ProblemBuilder {
let group_key = self.group_key_fn.take().unwrap_or_else(|| Box::new(|_| Box::new(|a| a.vehicle.profile.index)));
let fleet = Arc::new(Fleet::new(vec![driver], vehicles, group_key));

let logger = self.logger.unwrap_or_else(|| Arc::new(|msg| println!("{}", msg)));

// setup jobs
let jobs = Arc::new(Jobs::new(fleet.as_ref(), self.jobs, transport.as_ref()));
let jobs = Arc::new(Jobs::new(fleet.as_ref(), self.jobs, transport.as_ref(), &logger));

Ok(Problem { fleet, jobs, locks: vec![], goal, activity, transport, extras })
}
Expand Down
86 changes: 49 additions & 37 deletions vrp-core/src/models/problem/jobs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ mod jobs_test;
use crate::models::common::*;
use crate::models::problem::{Costs, Fleet, TransportCost};
use crate::utils::{short_type_name, Either};
use rosomaxa::prelude::Float;
use rosomaxa::utils::{compare_floats_f32, compare_floats_f32_refs};
use rosomaxa::prelude::{Float, InfoLogger};
use rosomaxa::utils::{compare_floats_f32, compare_floats_f32_refs, parallel_collect, Timer};
use std::cmp::Ordering::Less;
use std::collections::HashMap;
use std::fmt::{Debug, Formatter};
Expand Down Expand Up @@ -237,8 +237,8 @@ pub struct Jobs {

impl Jobs {
/// Creates a new [`Jobs`].
pub fn new(fleet: &Fleet, jobs: Vec<Job>, transport: &(dyn TransportCost)) -> Jobs {
Jobs { jobs: jobs.clone(), index: create_index(fleet, jobs, transport) }
pub fn new(fleet: &Fleet, jobs: Vec<Job>, transport: &(dyn TransportCost), logger: &InfoLogger) -> Jobs {
Jobs { jobs: jobs.clone(), index: create_index(fleet, jobs, transport, logger) }
}

/// Returns all jobs in original order.
Expand Down Expand Up @@ -303,46 +303,58 @@ pub fn get_job_locations(job: &Job) -> impl Iterator<Item = Option<Location>> +
}

/// Creates job index.
fn create_index(fleet: &Fleet, jobs: Vec<Job>, transport: &(dyn TransportCost)) -> HashMap<usize, JobIndex> {
fn create_index(
fleet: &Fleet,
jobs: Vec<Job>,
transport: &(dyn TransportCost),
logger: &InfoLogger,
) -> HashMap<usize, JobIndex> {
let avg_profile_costs = get_avg_profile_costs(fleet);

fleet.profiles.iter().fold(HashMap::new(), |mut acc, profile| {
let avg_costs = avg_profile_costs.get(&profile.index).unwrap();
// get all possible start positions for given profile
let starts: Vec<Location> = fleet
.vehicles
.iter()
.filter(|v| v.profile.index == profile.index)
.flat_map(|v| v.details.iter().map(|d| d.start.as_ref().map(|s| s.location)))
.flatten()
.collect();

// create job index
let item = jobs.iter().cloned().fold(HashMap::new(), |mut acc, job| {
let mut sorted_job_costs: Vec<(Job, LowPrecisionCost)> = jobs
let (job_index, duration) = Timer::measure_duration(|| {
fleet.profiles.iter().fold(HashMap::new(), |mut acc, profile| {
let avg_costs = avg_profile_costs.get(&profile.index).unwrap();
// get all possible start positions for given profile
let starts: Vec<Location> = fleet
.vehicles
.iter()
.filter(|j| **j != job)
.map(|j| (j.clone(), get_cost_between_jobs(profile, avg_costs, transport, &job, j)))
.filter(|v| v.profile.index == profile.index)
.flat_map(|v| v.details.iter().map(|d| d.start.as_ref().map(|s| s.location)))
.flatten()
.collect();
sorted_job_costs.sort_by(|(_, a), (_, b)| compare_floats_f32_refs(a, b));

sorted_job_costs.truncate(MAX_NEIGHBOURS);
sorted_job_costs.shrink_to_fit();

let fleet_costs = starts
.iter()
.cloned()
.map(|s| get_cost_between_job_and_location(profile, avg_costs, transport, &job, s))
.min_by(|a, b| a.partial_cmp(b).unwrap_or(Less))
.unwrap_or(DEFAULT_COST);

acc.insert(job, (sorted_job_costs, fleet_costs));
// create job index
let item = parallel_collect(&jobs, |job| {
let mut sorted_job_costs: Vec<(Job, LowPrecisionCost)> = jobs
.iter()
.filter(|j| **j != *job)
.map(|j| (j.clone(), get_cost_between_jobs(profile, avg_costs, transport, job, j)))
.collect();
sorted_job_costs.sort_by(|(_, a), (_, b)| compare_floats_f32_refs(a, b));

sorted_job_costs.truncate(MAX_NEIGHBOURS);
sorted_job_costs.shrink_to_fit();

let fleet_costs = starts
.iter()
.cloned()
.map(|s| get_cost_between_job_and_location(profile, avg_costs, transport, job, s))
.min_by(|a, b| a.partial_cmp(b).unwrap_or(Less))
.unwrap_or(DEFAULT_COST);

(job.clone(), (sorted_job_costs, fleet_costs))
})
.into_iter()
.collect::<HashMap<_, _>>();

acc.insert(profile.index, item);
acc
});
})
});

(logger)(format!("job index created in {}ms", duration.as_millis()).as_str());

acc.insert(profile.index, item);
acc
})
job_index
}

fn get_cost_between_locations(
Expand Down
3 changes: 2 additions & 1 deletion vrp-core/src/solver/processing/vicinity_clustering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ impl HeuristicContextProcessing for VicinityClustering {
fn pre_process(&self, context: Self::Context) -> Self::Context {
let problem = context.problem.clone();
let environment = context.environment.clone();
let logger = environment.logger.clone();

let config = if let Some(config) = problem.extras.get_cluster_config() { config } else { return context };

Expand All @@ -52,7 +53,7 @@ impl HeuristicContextProcessing for VicinityClustering {

let problem = Arc::new(Problem {
fleet: problem.fleet.clone(),
jobs: Arc::new(Jobs::new(problem.fleet.as_ref(), jobs, problem.transport.as_ref())),
jobs: Arc::new(Jobs::new(problem.fleet.as_ref(), jobs, problem.transport.as_ref(), &logger)),
locks: problem.locks.clone(),
goal: problem.goal.clone(),
activity: problem.activity.clone(),
Expand Down
4 changes: 2 additions & 2 deletions vrp-core/tests/helpers/construction/heuristics.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::construction::heuristics::*;
use crate::helpers::models::domain::{test_random, TestGoalContextBuilder};
use crate::helpers::models::domain::{test_logger, test_random, TestGoalContextBuilder};
use crate::helpers::models::problem::{test_fleet, TestActivityCost, TestTransportCost};
use crate::models::problem::Job;
use crate::models::solution::Registry;
Expand Down Expand Up @@ -103,7 +103,7 @@ impl TestInsertionContextBuilder {
fn create_empty_problem() -> Problem {
let transport = TestTransportCost::new_shared();
let fleet = Arc::new(test_fleet());
let jobs = Arc::new(Jobs::new(fleet.as_ref(), vec![], transport.as_ref()));
let jobs = Arc::new(Jobs::new(fleet.as_ref(), vec![], transport.as_ref(), &test_logger()));
Problem {
fleet,
jobs,
Expand Down
8 changes: 6 additions & 2 deletions vrp-core/tests/helpers/models/domain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ pub fn test_random() -> Arc<dyn Random> {
Arc::new(DefaultRandom::default())
}

pub fn test_logger() -> InfoLogger {
Arc::new(|_| ())
}

pub struct TestGoalContextBuilder {
features: Vec<Feature>,
}
Expand Down Expand Up @@ -74,7 +78,7 @@ impl ProblemBuilder {
}

pub fn with_jobs(&mut self, jobs: Vec<Job>) -> &mut Self {
self.0.jobs = Arc::new(Jobs::new(&self.0.fleet, jobs, self.0.transport.as_ref()));
self.0.jobs = Arc::new(Jobs::new(&self.0.fleet, jobs, self.0.transport.as_ref(), &test_logger()));
self
}

Expand Down Expand Up @@ -137,7 +141,7 @@ pub fn get_customer_id(job: &Job) -> String {
fn create_empty_problem() -> Problem {
let transport = TestTransportCost::new_shared();
let fleet = test_fleet();
let jobs = Jobs::new(&fleet, vec![], transport.as_ref());
let jobs = Jobs::new(&fleet, vec![], transport.as_ref(), &test_logger());

Problem {
fleet: Arc::new(fleet),
Expand Down
4 changes: 2 additions & 2 deletions vrp-core/tests/helpers/solver/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::algorithms::geometry::Point;
use crate::construction::features::TransportFeatureBuilder;
use crate::construction::heuristics::MoveContext;
use crate::helpers::models::domain::{test_random, TestGoalContextBuilder};
use crate::helpers::models::domain::{test_logger, test_random, TestGoalContextBuilder};
use crate::helpers::models::problem::*;
use crate::helpers::models::solution::{ActivityBuilder, RouteBuilder};
use crate::models::common::{Cost, Location};
Expand Down Expand Up @@ -155,7 +155,7 @@ pub fn generate_matrix_routes(
let matrix_data = MatrixData::new(0, None, durations, distances);
let transport = create_matrix_transport_cost(vec![matrix_data]).unwrap();
let activity = Arc::new(TestActivityCost::default());
let jobs = Jobs::new(&fleet, jobs, transport.as_ref());
let jobs = Jobs::new(&fleet, jobs, transport.as_ref(), &test_logger());

let problem = Problem {
fleet,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::*;
use crate::construction::features::*;
use crate::helpers::construction::features::{create_simple_demand, create_simple_dynamic_demand};
use crate::helpers::models::domain::TestGoalContextBuilder;
use crate::helpers::models::domain::{test_logger, TestGoalContextBuilder};
use crate::helpers::models::problem::*;
use crate::models::common::*;
use crate::models::problem::*;
Expand Down Expand Up @@ -101,7 +101,7 @@ fn create_test_problem(

Problem {
fleet: fleet.clone(),
jobs: Arc::new(Jobs::new(&fleet, jobs, transport.as_ref())),
jobs: Arc::new(Jobs::new(&fleet, jobs, transport.as_ref(), &test_logger())),
locks,
goal: Arc::new(goal),
activity,
Expand Down
14 changes: 9 additions & 5 deletions vrp-core/tests/unit/models/problem/jobs_test.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::*;
use crate::helpers::models::domain::test_logger;
use crate::helpers::models::problem::*;
use crate::models::problem::{TravelTime, VehicleDetail, VehiclePlace};
use crate::models::solution::Route;
Expand Down Expand Up @@ -99,7 +100,10 @@ fn create_costs() -> Costs {
fn all_returns_all_jobs() {
let jobs = vec![TestSingleBuilder::default().build_as_job_ref(), TestSingleBuilder::default().build_as_job_ref()];

assert_eq!(Jobs::new(&test_fleet(), jobs, create_only_distance_transport_cost().as_ref()).all().count(), 2)
assert_eq!(
Jobs::new(&test_fleet(), jobs, create_only_distance_transport_cost().as_ref(), &test_logger()).all().count(),
2
)
}

parameterized_test! {calculates_proper_cost_between_single_jobs, (left, right, expected), {
Expand Down Expand Up @@ -162,7 +166,7 @@ fn returns_proper_job_neighbours_impl(index: usize, expected: Vec<String>) {
TestSingleBuilder::default().id("s3").location(Some(3)).build_as_job_ref(),
TestSingleBuilder::default().id("s4").location(Some(4)).build_as_job_ref(),
];
let jobs = Jobs::new(&fleet, species.clone(), create_profile_aware_transport_cost().as_ref());
let jobs = Jobs::new(&fleet, species.clone(), create_profile_aware_transport_cost().as_ref(), &test_logger());

let result: Vec<String> =
jobs.neighbors(&p1, species.get(index).unwrap(), 0.0).map(|(j, _)| get_job_id(j).clone()).collect();
Expand Down Expand Up @@ -211,7 +215,7 @@ fn returns_proper_job_ranks_impl(index: usize, profile_index: usize, expected: D
TestSingleBuilder::default().id("s2").location(Some(21)).build_as_job_ref(),
TestSingleBuilder::default().id("s3").location(Some(31)).build_as_job_ref(),
];
let jobs = Jobs::new(&fleet, species.clone(), create_profile_aware_transport_cost().as_ref());
let jobs = Jobs::new(&fleet, species.clone(), create_profile_aware_transport_cost().as_ref(), &test_logger());

let result = jobs.rank(&profile, species.get(index).unwrap());

Expand All @@ -223,7 +227,7 @@ fn can_use_multi_job_bind_and_roots() {
let job = test_multi_job_with_locations(vec![vec![Some(0)], vec![Some(1)]]);
let jobs = vec![Job::Multi(job.clone())];

let jobs = Jobs::new(&test_fleet(), jobs, create_only_distance_transport_cost().as_ref());
let jobs = Jobs::new(&test_fleet(), jobs, create_only_distance_transport_cost().as_ref(), &test_logger());
let job = Job::Multi(Multi::roots(job.jobs.first().unwrap()).unwrap());

assert_eq!(jobs.neighbors(&Profile::default(), &job, 0.0).count(), 0);
Expand All @@ -247,7 +251,7 @@ fn can_handle_negative_distances_durations_impl(transport_costs: Arc<dyn Transpo
TestSingleBuilder::default().id("s1").location(Some(1)).build_as_job_ref(),
];

let jobs = Jobs::new(&test_fleet(), species.clone(), transport_costs.as_ref());
let jobs = Jobs::new(&test_fleet(), species.clone(), transport_costs.as_ref(), &test_logger());

for job in &species {
assert!(jobs
Expand Down
7 changes: 5 additions & 2 deletions vrp-pragmatic/src/format/problem/job_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,18 @@ pub(super) fn read_jobs_with_extra_locks(
fleet: &Fleet,
transport: &(dyn TransportCost + Sync + Send),
job_index: &mut JobIndex,
random: &Arc<dyn Random>,
environment: &Environment,
) -> (Jobs, Vec<Arc<Lock>>) {
let random = &environment.random;
let logger = &environment.logger;

let (mut jobs, mut locks) = read_required_jobs(api_problem, props, coord_index, job_index, random);
let (conditional_jobs, conditional_locks) = read_conditional_jobs(api_problem, coord_index, job_index);

jobs.extend(conditional_jobs);
locks.extend(conditional_locks);

(Jobs::new(fleet, jobs, transport), locks)
(Jobs::new(fleet, jobs, transport, logger), locks)
}

pub(super) fn read_locks(api_problem: &ApiProblem, job_index: &JobIndex) -> Vec<Arc<Lock>> {
Expand Down
7 changes: 3 additions & 4 deletions vrp-pragmatic/src/format/problem/problem_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,17 +214,16 @@ fn get_problem_blocks(
})?
};

// TODO pass random from outside as there might be need to have it initialized with seed
// at the moment, this random instance is used only by multi job permutation generator
let random: Arc<dyn Random> = Arc::new(DefaultRandom::default());
// TODO pass environment from outside to allow parametrization
let environment = Environment::default();
let (jobs, locks) = read_jobs_with_extra_locks(
api_problem,
problem_props,
&coord_index,
&fleet,
transport.as_ref(),
job_index,
&random,
&environment,
);
let locks = locks.into_iter().chain(read_locks(api_problem, job_index)).collect::<Vec<_>>();

Expand Down
Loading

0 comments on commit ca3d032

Please sign in to comment.