├── .codecov.yml ├── .dockerignore ├── .gitattributes ├── .github └── workflows │ ├── audit.yaml │ ├── build.yaml │ ├── coverage.yaml │ ├── discovery.yaml │ ├── experiments.yaml │ ├── maturin.yaml │ └── publish.yaml ├── .gitignore ├── .rustfmt.toml ├── CHANGELOG.md ├── CITATION.cff ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── README.md ├── docs ├── .gitignore ├── book.toml ├── resources │ ├── leaflet.css │ ├── leaflet.js │ ├── map.js │ └── vrp-example.png └── src │ ├── SUMMARY.md │ ├── concepts │ ├── index.md │ ├── pragmatic │ │ ├── errors │ │ │ └── index.md │ │ ├── index.md │ │ ├── problem │ │ │ ├── clustering.md │ │ │ ├── index.md │ │ │ ├── jobs.md │ │ │ ├── objectives.md │ │ │ ├── relations.md │ │ │ ├── resources.md │ │ │ └── vehicles.md │ │ ├── routing │ │ │ ├── format.md │ │ │ ├── index.md │ │ │ └── profile.md │ │ └── solution │ │ │ ├── index.md │ │ │ ├── statistic.md │ │ │ ├── tour-list.md │ │ │ ├── unassigned-jobs.md │ │ │ └── violations.md │ └── scientific │ │ ├── index.md │ │ ├── lilim.md │ │ ├── solomon.md │ │ └── tsplib.md │ ├── examples │ ├── index.md │ ├── interop │ │ ├── index.md │ │ ├── java.md │ │ ├── javascript.md │ │ ├── kotlin.md │ │ └── python.md │ └── pragmatic │ │ ├── basics │ │ ├── break.md │ │ ├── index.md │ │ ├── job-priorities.md │ │ ├── job-types.md │ │ ├── multi-day.md │ │ ├── profiles.md │ │ ├── recharge.md │ │ ├── relations.md │ │ ├── reload.md │ │ ├── skills.md │ │ └── unassigned.md │ │ ├── clustering │ │ ├── index.md │ │ ├── vicinity-continue.md │ │ └── vicinity-return.md │ │ ├── index.md │ │ └── objectives │ │ ├── index.md │ │ ├── objective-balance-activities.md │ │ ├── objective-balance-distance.md │ │ ├── objective-balance-max-load.md │ │ └── objective-default.md │ ├── getting-started │ ├── analysis.md │ ├── features.md │ ├── import.md │ ├── index.md │ ├── installation.md │ ├── performance.md │ ├── routing.md │ └── solver.md │ ├── images │ └── rosomaxa.gif │ ├── index.md │ └── internals │ ├── algorithms │ ├── heuristics.md │ ├── index.md │ └── rosomaxa.md │ ├── development │ ├── best-practices.md │ ├── extension.md │ ├── index.md │ ├── structure.md │ └── testing.md │ ├── index.md │ └── overview.md ├── examples ├── README.md ├── data │ ├── config │ │ ├── config.full.json │ │ └── config.telemetry.json │ ├── csv │ │ ├── jobs.csv │ │ └── vehicles.csv │ ├── pragmatic │ │ ├── basics │ │ │ ├── break.basic.problem.json │ │ │ ├── break.basic.solution.json │ │ │ ├── break.required.problem.json │ │ │ ├── break.required.solution.json │ │ │ ├── location.basic.problem.json │ │ │ ├── location.basic.solution.json │ │ │ ├── multi-day.basic.problem.json │ │ │ ├── multi-day.basic.solution.json │ │ │ ├── multi-job.basic.problem.json │ │ │ ├── multi-job.basic.solution.json │ │ │ ├── multi-job.mixed.problem.json │ │ │ ├── multi-job.mixed.solution.json │ │ │ ├── multi-objective.balance-load.problem.json │ │ │ ├── multi-objective.default.problem.json │ │ │ ├── multi-objective.maximize-value.problem.json │ │ │ ├── priorities.value.problem.json │ │ │ ├── priorities.value.solution.geojson │ │ │ ├── priorities.value.solution.json │ │ │ ├── profiles.basic.locations.json │ │ │ ├── profiles.basic.matrix.car.json │ │ │ ├── profiles.basic.matrix.truck.json │ │ │ ├── profiles.basic.problem.json │ │ │ ├── profiles.basic.solution.json │ │ │ ├── recharge.basic.problem.json │ │ │ ├── recharge.basic.solution.json │ │ │ ├── recharge.solution.geojson │ │ │ ├── relation-any.basic.problem.json │ │ │ ├── relation-any.basic.solution.json │ │ │ ├── relation-strict.basic.problem.json │ │ │ ├── relation-strict.basic.solution.json │ │ │ ├── reload.basic.problem.json │ │ │ ├── reload.basic.solution.json │ │ │ ├── reload.multi.problem.json │ │ │ ├── reload.multi.solution.geojson │ │ │ ├── reload.multi.solution.json │ │ │ ├── reload.resource.problem.json │ │ │ ├── reload.resource.solution.json │ │ │ ├── skills.basic.problem.json │ │ │ ├── skills.basic.solution.json │ │ │ ├── unassigned.unreachable.problem.json │ │ │ └── unassigned.unreachable.solution.json │ │ ├── benches │ │ │ ├── multi-job.100.json │ │ │ ├── simple.deliveries.100.json │ │ │ └── simple.reload.100.json │ │ ├── clustering │ │ │ ├── berlin.vicinity-continue.matrix.json │ │ │ ├── berlin.vicinity-continue.problem.json │ │ │ ├── berlin.vicinity-continue.solution.geojson │ │ │ ├── berlin.vicinity-continue.solution.json │ │ │ ├── berlin.vicinity-return.matrix.json │ │ │ ├── berlin.vicinity-return.problem.json │ │ │ ├── berlin.vicinity-return.solution.geojson │ │ │ └── berlin.vicinity-return.solution.json │ │ ├── objectives │ │ │ ├── berlin.balance-activities.problem.json │ │ │ ├── berlin.balance-activities.solution.geojson │ │ │ ├── berlin.balance-activities.solution.json │ │ │ ├── berlin.balance-distance.problem.json │ │ │ ├── berlin.balance-distance.solution.geojson │ │ │ ├── berlin.balance-distance.solution.json │ │ │ ├── berlin.balance-max-load.problem.json │ │ │ ├── berlin.balance-max-load.solution.geojson │ │ │ ├── berlin.balance-max-load.solution.json │ │ │ ├── berlin.default.problem.json │ │ │ ├── berlin.default.solution.geojson │ │ │ └── berlin.default.solution.json │ │ ├── simple.basic.locations.json │ │ ├── simple.basic.matrix.json │ │ ├── simple.basic.problem.json │ │ ├── simple.basic.solution.geojson │ │ ├── simple.basic.solution.json │ │ └── simple.index.problem.json │ └── scientific │ │ ├── lilim │ │ ├── LC101.txt │ │ └── LC1_10_2.txt │ │ ├── solomon │ │ ├── C101.100.best.txt │ │ ├── C101.100.partial.txt │ │ ├── C101.100.txt │ │ └── C101.25.txt │ │ └── tsplib │ │ ├── A-n32-k5.vrp │ │ └── example.txt ├── json-pragmatic │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── main.rs ├── jvm-interop │ ├── .gitignore │ ├── README.md │ ├── build.gradle │ └── src │ │ └── main │ │ ├── java │ │ └── vrp │ │ │ └── example │ │ │ └── java │ │ │ └── Application.java │ │ ├── kotlin │ │ └── vrp │ │ │ └── example │ │ │ └── kotlin │ │ │ └── Application.kt │ │ └── resources │ │ └── .keep └── python-interop │ ├── config_types.py │ ├── example.py │ ├── pragmatic_types.py │ └── tutorial.ipynb ├── experiments ├── etc │ ├── bench-scientific.py │ ├── collect-metrics.py │ ├── rosomaxa.py │ └── shared.py ├── gpt │ ├── vrp_ai.ipynb │ └── vrp_ai │ │ ├── encoder.py │ │ ├── gpt.py │ │ ├── instances.py │ │ ├── quadkey.py │ │ ├── solver.py │ │ └── tokenizer.py └── heuristic-research │ ├── Cargo.toml │ ├── README.md │ ├── src │ ├── lib.rs │ ├── main.rs │ ├── plots │ │ ├── config.rs │ │ ├── drawing │ │ │ ├── draw_fitness.rs │ │ │ ├── draw_population.rs │ │ │ ├── draw_search.rs │ │ │ ├── draw_solution.rs │ │ │ └── mod.rs │ │ └── mod.rs │ └── solver │ │ ├── mod.rs │ │ ├── proxies.rs │ │ ├── state.rs │ │ ├── vector │ │ ├── mod.rs │ │ └── objectives.rs │ │ └── vrp │ │ ├── mod.rs │ │ └── population.rs │ ├── tests │ └── unit │ │ └── solver │ │ ├── vector │ │ └── objectives_test.rs │ │ └── vrp │ │ └── vrp_test.rs │ └── www │ ├── bootstrap.js │ ├── index.html │ ├── index.js │ └── style.css ├── rosomaxa ├── Cargo.toml ├── README.md ├── src │ ├── algorithms │ │ ├── gsom │ │ │ ├── contraction.rs │ │ │ ├── mod.rs │ │ │ ├── network.rs │ │ │ ├── node.rs │ │ │ └── state.rs │ │ ├── math │ │ │ ├── distance.rs │ │ │ ├── mod.rs │ │ │ ├── remedian.rs │ │ │ └── statistics.rs │ │ ├── mod.rs │ │ └── rl │ │ │ ├── mod.rs │ │ │ └── slot_machine.rs │ ├── evolution │ │ ├── config.rs │ │ ├── mod.rs │ │ ├── objectives.rs │ │ ├── simulator.rs │ │ ├── strategies │ │ │ ├── iterative.rs │ │ │ └── mod.rs │ │ └── telemetry.rs │ ├── example.rs │ ├── hyper │ │ ├── dynamic_selective.rs │ │ ├── mod.rs │ │ └── static_selective.rs │ ├── lib.rs │ ├── population │ │ ├── elitism.rs │ │ ├── greedy.rs │ │ ├── mod.rs │ │ └── rosomaxa.rs │ ├── prelude.rs │ ├── termination │ │ ├── max_generation.rs │ │ ├── max_time.rs │ │ ├── min_variation.rs │ │ ├── mod.rs │ │ └── target_proximity.rs │ └── utils │ │ ├── environment.rs │ │ ├── error.rs │ │ ├── iterators.rs │ │ ├── mod.rs │ │ ├── noise.rs │ │ ├── parallel.rs │ │ ├── random.rs │ │ ├── timing.rs │ │ └── types.rs └── tests │ ├── helpers │ ├── algorithms │ │ ├── gsom │ │ │ └── mod.rs │ │ └── mod.rs │ ├── example.rs │ ├── macros.rs │ ├── mod.rs │ └── utils │ │ └── mod.rs │ └── unit │ ├── algorithms │ ├── gsom │ │ ├── contraction_test.rs │ │ ├── network_test.rs │ │ ├── node_test.rs │ │ └── state_test.rs │ ├── math │ │ ├── distance_test.rs │ │ └── remedian_test.rs │ └── rl │ │ └── slot_machine_test.rs │ ├── evolution │ └── telemetry_test.rs │ ├── example_test.rs │ ├── hyper │ └── dynamic_selective_test.rs │ ├── population │ ├── elitism_test.rs │ ├── greedy_test.rs │ └── rosomaxa_test.rs │ ├── termination │ ├── max_generation_test.rs │ ├── min_variation_test.rs │ └── target_proximity_test.rs │ └── utils │ ├── iterators_test.rs │ ├── parallel_test.rs │ ├── random_test.rs │ └── types_test.rs ├── solve_problem.sh ├── vrp-cli ├── Cargo.toml ├── README.md ├── pyproject.toml ├── src │ ├── commands │ │ ├── analyze.rs │ │ ├── check.rs │ │ ├── generate.rs │ │ ├── import.rs │ │ ├── mod.rs │ │ └── solve.rs │ ├── extensions │ │ ├── analyze │ │ │ ├── clusters.rs │ │ │ └── mod.rs │ │ ├── check │ │ │ └── mod.rs │ │ ├── generate │ │ │ ├── fleet.rs │ │ │ ├── mod.rs │ │ │ ├── plan.rs │ │ │ └── prototype.rs │ │ ├── import │ │ │ ├── csv.rs │ │ │ └── mod.rs │ │ ├── mod.rs │ │ └── solve │ │ │ ├── config.rs │ │ │ ├── formats.rs │ │ │ └── mod.rs │ ├── lib.rs │ └── main.rs └── tests │ ├── features │ ├── generate.rs │ ├── mod.rs │ └── solve.rs │ ├── helpers │ ├── generate.rs │ └── mod.rs │ └── unit │ ├── commands │ ├── analyze_test.rs │ ├── check_test.rs │ ├── generate_test.rs │ ├── import_test.rs │ └── solve_test.rs │ ├── extensions │ ├── analyze │ │ └── clusters_test.rs │ ├── check │ │ └── check_test.rs │ ├── generate │ │ ├── fleet_test.rs │ │ ├── plan_test.rs │ │ └── prototype_test.rs │ ├── import │ │ └── csv_test.rs │ └── solve │ │ └── config_test.rs │ ├── lib_test.rs │ └── main_test.rs ├── vrp-core ├── Cargo.toml ├── README.md ├── examples │ ├── README.md │ ├── common │ │ └── routing.rs │ ├── custom_constraint.rs │ ├── custom_objective.rs │ ├── cvrp.rs │ └── pdptw.rs ├── src │ ├── algorithms │ │ ├── clustering │ │ │ ├── dbscan.rs │ │ │ ├── kmedoids.rs │ │ │ └── mod.rs │ │ ├── geometry │ │ │ ├── mod.rs │ │ │ └── point.rs │ │ ├── lkh │ │ │ ├── kopt.rs │ │ │ ├── mod.rs │ │ │ └── tour.rs │ │ ├── mod.rs │ │ └── structures │ │ │ ├── bitvec.rs │ │ │ └── mod.rs │ ├── construction │ │ ├── clustering │ │ │ ├── dbscan │ │ │ │ ├── mod.rs │ │ │ │ └── neighbour_clusters.rs │ │ │ ├── kmedoids │ │ │ │ ├── mod.rs │ │ │ │ └── multi_tier_clusters.rs │ │ │ ├── mod.rs │ │ │ └── vicinity │ │ │ │ ├── estimations.rs │ │ │ │ └── mod.rs │ │ ├── enablers │ │ │ ├── conditional_job.rs │ │ │ ├── departure_time.rs │ │ │ ├── feature_combinator.rs │ │ │ ├── mod.rs │ │ │ ├── multi_trip.rs │ │ │ ├── only_vehicle_activity_cost.rs │ │ │ ├── reserved_time.rs │ │ │ ├── route_intervals.rs │ │ │ ├── schedule_update.rs │ │ │ ├── travel_info.rs │ │ │ └── typed_actor_group_key.rs │ │ ├── features │ │ │ ├── breaks.rs │ │ │ ├── capacity.rs │ │ │ ├── compatibility.rs │ │ │ ├── fast_service.rs │ │ │ ├── fleet_usage.rs │ │ │ ├── groups.rs │ │ │ ├── hierarchical_areas.rs │ │ │ ├── known_edge.rs │ │ │ ├── locked_jobs.rs │ │ │ ├── minimize_unassigned.rs │ │ │ ├── mod.rs │ │ │ ├── reachable.rs │ │ │ ├── recharge.rs │ │ │ ├── reloads.rs │ │ │ ├── skills.rs │ │ │ ├── total_value.rs │ │ │ ├── tour_compactness.rs │ │ │ ├── tour_limits.rs │ │ │ ├── tour_order.rs │ │ │ ├── transport.rs │ │ │ └── work_balance.rs │ │ ├── heuristics │ │ │ ├── context.rs │ │ │ ├── evaluators.rs │ │ │ ├── factories.rs │ │ │ ├── insertions.rs │ │ │ ├── metrics.rs │ │ │ ├── mod.rs │ │ │ └── selectors.rs │ │ ├── mod.rs │ │ └── probing │ │ │ ├── mod.rs │ │ │ └── repair_solution.rs │ ├── lib.rs │ ├── macros.rs │ ├── models │ │ ├── common │ │ │ ├── dimens.rs │ │ │ ├── domain.rs │ │ │ ├── footprint.rs │ │ │ ├── load.rs │ │ │ ├── mod.rs │ │ │ └── primitives.rs │ │ ├── domain.rs │ │ ├── examples.rs │ │ ├── extras.rs │ │ ├── goal.rs │ │ ├── mod.rs │ │ ├── problem │ │ │ ├── builders.rs │ │ │ ├── costs.rs │ │ │ ├── fleet.rs │ │ │ ├── jobs.rs │ │ │ └── mod.rs │ │ └── solution │ │ │ ├── mod.rs │ │ │ ├── registry.rs │ │ │ ├── route.rs │ │ │ └── tour.rs │ ├── prelude.rs │ ├── solver │ │ ├── heuristic.rs │ │ ├── mod.rs │ │ ├── processing │ │ │ ├── advance_departure.rs │ │ │ ├── mod.rs │ │ │ ├── reschedule_reserved_time.rs │ │ │ ├── unassignment_reason.rs │ │ │ └── vicinity_clustering.rs │ │ └── search │ │ │ ├── decompose_search.rs │ │ │ ├── infeasible_search.rs │ │ │ ├── lkh_search.rs │ │ │ ├── local │ │ │ ├── exchange_inter_route.rs │ │ │ ├── exchange_intra_route.rs │ │ │ ├── exchange_sequence.rs │ │ │ ├── exchange_swap_star.rs │ │ │ ├── mod.rs │ │ │ └── reschedule_departure.rs │ │ │ ├── local_search.rs │ │ │ ├── mod.rs │ │ │ ├── recreate │ │ │ ├── mod.rs │ │ │ ├── recreate_with_blinks.rs │ │ │ ├── recreate_with_cheapest.rs │ │ │ ├── recreate_with_farthest.rs │ │ │ ├── recreate_with_gaps.rs │ │ │ ├── recreate_with_nearest_neighbor.rs │ │ │ ├── recreate_with_perturbation.rs │ │ │ ├── recreate_with_regret.rs │ │ │ ├── recreate_with_skip_best.rs │ │ │ ├── recreate_with_skip_random.rs │ │ │ └── recreate_with_slice.rs │ │ │ ├── redistribute_search.rs │ │ │ ├── ruin │ │ │ ├── adjusted_string_removal.rs │ │ │ ├── cluster_removal.rs │ │ │ ├── mod.rs │ │ │ ├── neighbour_removal.rs │ │ │ ├── random_job_removal.rs │ │ │ ├── route_removal.rs │ │ │ └── worst_jobs_removal.rs │ │ │ ├── ruin_recreate.rs │ │ │ └── utils │ │ │ ├── mod.rs │ │ │ ├── removal.rs │ │ │ ├── selection.rs │ │ │ ├── tabu_list.rs │ │ │ └── termination.rs │ └── utils │ │ ├── mod.rs │ │ └── types.rs └── tests │ ├── helpers │ ├── construction │ │ ├── clustering │ │ │ ├── dbscan.rs │ │ │ ├── mod.rs │ │ │ └── vicinity.rs │ │ ├── features.rs │ │ ├── heuristics.rs │ │ └── mod.rs │ ├── macros.rs │ ├── mod.rs │ ├── models │ │ ├── domain.rs │ │ ├── mod.rs │ │ ├── problem │ │ │ ├── costs.rs │ │ │ ├── fleet.rs │ │ │ ├── jobs.rs │ │ │ └── mod.rs │ │ └── solution │ │ │ ├── actor.rs │ │ │ ├── mod.rs │ │ │ └── route.rs │ ├── solver │ │ ├── mod.rs │ │ └── mutation.rs │ └── utils │ │ ├── mod.rs │ │ └── random.rs │ └── unit │ ├── algorithms │ ├── clustering │ │ ├── dbscan_test.rs │ │ └── kmedoids_test.rs │ ├── geometry │ │ └── point_test.rs │ ├── lkh │ │ ├── kopt_test.rs │ │ └── tour_test.rs │ └── structures │ │ └── bitvec_test.rs │ ├── construction │ ├── clustering │ │ ├── dbscan │ │ │ └── neighbour_clusters_test.rs │ │ ├── kmedoids │ │ │ └── multi_tier_clusters_test.rs │ │ └── vicinity │ │ │ ├── estimations_test.rs │ │ │ └── vicinity_test.rs │ ├── enablers │ │ ├── departure_time_test.rs │ │ └── reserved_time_test.rs │ ├── features │ │ ├── breaks_test.rs │ │ ├── capacity_test.rs │ │ ├── compatibility_test.rs │ │ ├── fast_service_test.rs │ │ ├── fleet_usage_test.rs │ │ ├── groups_test.rs │ │ ├── hierarchical_areas_test.rs │ │ ├── locked_jobs_test.rs │ │ ├── minimize_unassigned_test.rs │ │ ├── recharge_test.rs │ │ ├── reloads_test.rs │ │ ├── skills_test.rs │ │ ├── total_value_test.rs │ │ ├── tour_compactness_test.rs │ │ ├── tour_limits_test.rs │ │ ├── tour_order_test.rs │ │ └── transport_test.rs │ ├── heuristics │ │ ├── context_test.rs │ │ ├── evaluators_test.rs │ │ ├── insertions_test.rs │ │ ├── metrics_test.rs │ │ └── selectors_test.rs │ └── probing │ │ └── repair_solution_test.rs │ ├── models │ ├── common │ │ ├── domain_test.rs │ │ ├── footprint_test.rs │ │ └── load_test.rs │ ├── goal_test.rs │ ├── problem │ │ ├── costs_test.rs │ │ ├── fleet_test.rs │ │ └── jobs_test.rs │ └── solution │ │ ├── actor_test.rs │ │ └── tour_test.rs │ └── solver │ ├── processing │ ├── unassignment_reason_test.rs │ └── vicinity_clustering_test.rs │ └── search │ ├── decompose_search_test.rs │ ├── lkh_search_test.rs │ ├── local │ ├── exchange_inter_route_test.rs │ ├── exchange_sequence_test.rs │ └── exchange_swap_star_test.rs │ ├── redistribute_search_test.rs │ ├── ruin │ ├── adjusted_string_removal_test.rs │ ├── cluster_removal_test.rs │ ├── route_removal_test.rs │ └── worst_jobs_removal_test.rs │ └── utils │ └── removal_test.rs ├── vrp-pragmatic ├── Cargo.toml ├── README.md ├── benches │ └── pragmatic_simple.rs ├── src │ ├── checker │ │ ├── assignment.rs │ │ ├── breaks.rs │ │ ├── capacity.rs │ │ ├── limits.rs │ │ ├── mod.rs │ │ ├── relations.rs │ │ └── routing.rs │ ├── format │ │ ├── coord_index.rs │ │ ├── dimensions.rs │ │ ├── location_fallback.rs │ │ ├── mod.rs │ │ ├── problem │ │ │ ├── clustering_reader.rs │ │ │ ├── fleet_reader.rs │ │ │ ├── goal_reader.rs │ │ │ ├── job_reader.rs │ │ │ ├── mod.rs │ │ │ ├── model.rs │ │ │ └── problem_reader.rs │ │ └── solution │ │ │ ├── activity_matcher.rs │ │ │ ├── break_writer.rs │ │ │ ├── extensions.rs │ │ │ ├── geo_serializer.rs │ │ │ ├── initial_reader.rs │ │ │ ├── mod.rs │ │ │ ├── model.rs │ │ │ └── solution_writer.rs │ ├── lib.rs │ ├── utils │ │ ├── approx_transportation.rs │ │ ├── collections.rs │ │ ├── mod.rs │ │ └── permutations.rs │ └── validation │ │ ├── common.rs │ │ ├── jobs.rs │ │ ├── mod.rs │ │ ├── objectives.rs │ │ ├── relations.rs │ │ ├── routing.rs │ │ └── vehicles.rs └── tests │ ├── discovery │ ├── mod.rs │ └── property │ │ ├── generated_with_breaks.rs │ │ ├── generated_with_clustering.rs │ │ ├── generated_with_groups.rs │ │ ├── generated_with_recharge.rs │ │ ├── generated_with_relations.rs │ │ ├── generated_with_reload.rs │ │ └── mod.rs │ ├── features │ ├── breaks │ │ ├── basic_break_test.rs │ │ ├── break_with_multiple_locations.rs │ │ ├── interval_break_test.rs │ │ ├── mod.rs │ │ ├── multi_break_test.rs │ │ ├── open_end_by_interval_break.rs │ │ ├── policy_break_test.rs │ │ ├── relation_break_test.rs │ │ └── required_break.rs │ ├── capacity │ │ ├── mod.rs │ │ └── simple_capacity_test.rs │ ├── clustering │ │ ├── basic_vicinity_test.rs │ │ ├── capacity_vicinity_test.rs │ │ ├── combination_vicinity_test.rs │ │ ├── mod.rs │ │ ├── profile_vicinity_test.rs │ │ └── specific_vicinity_test.rs │ ├── compatibility │ │ ├── basic_compatibility.rs │ │ └── mod.rs │ ├── fleet │ │ ├── basic_multi_shift.rs │ │ ├── basic_open_end.rs │ │ ├── mod.rs │ │ ├── multi_dimens.rs │ │ ├── profile_variation.rs │ │ └── unreachable_jobs.rs │ ├── format │ │ ├── location_custom.rs │ │ ├── location_index.rs │ │ └── mod.rs │ ├── group │ │ ├── basic_group.rs │ │ └── mod.rs │ ├── limits │ │ ├── max_distance.rs │ │ ├── max_duration.rs │ │ ├── mod.rs │ │ └── tour_size.rs │ ├── mod.rs │ ├── multjob │ │ ├── basic_multi_job.rs │ │ ├── basic_replacement.rs │ │ ├── basic_service.rs │ │ ├── limited_capacity.rs │ │ ├── mod.rs │ │ ├── single_type_places.rs │ │ └── unassigned_multi_job.rs │ ├── pickdev │ │ ├── basic_pick_dev.rs │ │ ├── mixed_pick_dev_simple_jobs.rs │ │ ├── mod.rs │ │ └── relation_pick_dev.rs │ ├── priorities │ │ ├── basic_order.rs │ │ ├── basic_value.rs │ │ └── mod.rs │ ├── recharge │ │ ├── basic_recharge.rs │ │ └── mod.rs │ ├── relations │ │ ├── any_basic.rs │ │ ├── any_with_new_jobs.rs │ │ ├── mixed_strict_any.rs │ │ ├── mixed_strict_sequence.rs │ │ ├── mod.rs │ │ ├── sequence_with_new_jobs.rs │ │ ├── strict_with_new_jobs.rs │ │ └── strict_with_old_jobs.rs │ ├── reload │ │ ├── avoid_reload.rs │ │ ├── basic_reload.rs │ │ ├── diff_reload_places.rs │ │ ├── mod.rs │ │ ├── multi_dim_reload.rs │ │ ├── multi_job_reload.rs │ │ ├── multi_vehicle_reload.rs │ │ ├── picks_devs_reload.rs │ │ └── shared_reload.rs │ ├── skills │ │ ├── basic_skill.rs │ │ ├── mod.rs │ │ └── unassigned_due_to_skills.rs │ ├── timing │ │ ├── basic_multiple_times.rs │ │ ├── basic_waiting_time.rs │ │ ├── mod.rs │ │ ├── strict_leads_to_unassigned.rs │ │ └── strict_split_into_two_tours.rs │ ├── tour_shape │ │ ├── basic_tour_compactness.rs │ │ └── mod.rs │ ├── unassigned │ │ ├── mod.rs │ │ ├── multi_reasons.rs │ │ └── single_reason.rs │ └── work_balance │ │ ├── balance_activities.rs │ │ ├── balance_max_load.rs │ │ ├── balance_transport.rs │ │ └── mod.rs │ ├── generator │ ├── common.rs │ ├── defaults.rs │ ├── jobs.rs │ ├── mod.rs │ ├── relations.rs │ └── vehicles.rs │ ├── helpers │ ├── core.rs │ ├── fixtures.rs │ ├── mod.rs │ ├── problem.rs │ ├── solution.rs │ └── solver.rs │ ├── regression │ ├── break_test.rs │ ├── mod.rs │ └── pd_same_stops.rs │ └── unit │ ├── checker │ ├── assignment_test.rs │ ├── breaks_test.rs │ ├── capacity_test.rs │ ├── checker_test.rs │ ├── limits_test.rs │ ├── relations_test.rs │ └── routing_test.rs │ ├── format │ ├── coord_index_test.rs │ ├── problem │ │ ├── fleet_reader_test.rs │ │ ├── model_test.rs │ │ └── reader_test.rs │ └── solution │ │ ├── geo_serializer_test.rs │ │ ├── initial_reader_test.rs │ │ └── writer_test.rs │ ├── utils │ ├── approx_transportation_test.rs │ └── permutations_test.rs │ └── validation │ ├── jobs_test.rs │ ├── objectives_test.rs │ ├── relations_test.rs │ ├── routing_test.rs │ └── vehicles_test.rs └── vrp-scientific ├── Cargo.toml ├── README.md ├── benches └── solomon_goal.rs ├── src ├── common │ ├── initial_reader.rs │ ├── mod.rs │ ├── routing.rs │ ├── text_reader.rs │ └── text_writer.rs ├── lib.rs ├── lilim │ ├── mod.rs │ ├── reader.rs │ └── writer.rs ├── solomon │ ├── mod.rs │ ├── reader.rs │ └── writer.rs └── tsplib │ ├── mod.rs │ ├── reader.rs │ └── writer.rs └── tests ├── helpers ├── analysis.rs ├── mod.rs └── solomon.rs ├── integration └── known_problems_test.rs └── unit ├── common ├── init_solution_reader_test.rs ├── routing_test.rs └── text_writer_test.rs ├── lilim └── reader_test.rs ├── solomon └── reader_test.rs └── tsplib └── reader_test.rs /.codecov.yml: -------------------------------------------------------------------------------- 1 | # For more configuration details: 2 | # https://docs.codecov.io/docs/codecov-yaml 3 | # Validation check: 4 | # curl -X POST --data-binary @.codecov.yml https://codecov.io/validate 5 | 6 | coverage: 7 | status: 8 | patch: false 9 | project: 10 | default: 11 | threshold: 0.1% 12 | 13 | range: 70..90 14 | round: down 15 | precision: 2 16 | 17 | ignore: 18 | - docs/.* 19 | - vrp-core/examples/.* 20 | 21 | comment: 22 | layout: diff, files -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | target -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | experiments/etc/** linguist-vendored 2 | experiments/gpt/** linguist-vendored 3 | experiments/heuristic-research/www/** linguist-vendored -------------------------------------------------------------------------------- /.github/workflows/audit.yaml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**/Cargo.toml' 7 | - '**/Cargo.lock' 8 | jobs: 9 | security-audit: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v1 13 | - uses: actions-rs/audit-check@v1 14 | with: 15 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | test-build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Check out the repo 14 | uses: actions/checkout@v2 15 | 16 | - name: Build and run tests 17 | run: cargo test --verbose --benches 18 | 19 | - name: Build and run examples 20 | run: | 21 | cargo run --example cvrp 22 | cargo run --example pdptw 23 | cargo run --example custom_constraint 24 | cargo run --example custom_objective 25 | 26 | - name: Run clippy 27 | uses: actions-rs/cargo@v1 28 | with: 29 | command: clippy 30 | args: --all-features --tests --examples -- -D warnings 31 | 32 | release-build: 33 | runs-on: ubuntu-latest 34 | steps: 35 | - name: Check out the repo 36 | uses: actions/checkout@v2 37 | 38 | - name: Release build 39 | run: cargo build --release --verbose 40 | 41 | wasm-build: 42 | runs-on: ubuntu-latest 43 | steps: 44 | - name: Check out the repo 45 | uses: actions/checkout@v2 46 | 47 | - name: Build WebAssembly 48 | run: | 49 | curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh 50 | cd vrp-cli 51 | wasm-pack build --target web 52 | 53 | - name: Upload WebAssembly artifact 54 | uses: actions/upload-artifact@v4 55 | with: 56 | name: vrp_cli_wasm 57 | path: vrp-cli/pkg/ -------------------------------------------------------------------------------- /.github/workflows/coverage.yaml: -------------------------------------------------------------------------------- 1 | name: Measure code coverage 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | # we set `opt-level = 1` in profile.test globally, but this affects code coverage, so we reset it here 10 | env: 11 | CARGO_PROFILE_TEST_OPT_LEVEL: 0 12 | 13 | jobs: 14 | measure-test-coverage: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Check out the repo 18 | uses: actions/checkout@v2 19 | 20 | - name: Install stable toolchain 21 | uses: actions-rs/toolchain@v1 22 | with: 23 | toolchain: stable 24 | override: true 25 | 26 | - name: Generate code coverage 27 | run: | 28 | cargo install --features vendored-openssl cargo-tarpaulin 29 | cargo tarpaulin --exclude-files *_test.rs *tests* *benches* *heuristic-research* --out xml 30 | 31 | - name: Upload to codecov.io 32 | uses: codecov/codecov-action@v1.0.2 33 | with: 34 | token: ${{secrets.CODECOV_TOKEN}} 35 | 36 | - name: Archive code coverage results 37 | uses: actions/upload-artifact@v4 38 | with: 39 | name: code-coverage-report 40 | path: cobertura.xml 41 | -------------------------------------------------------------------------------- /.github/workflows/discovery.yaml: -------------------------------------------------------------------------------- 1 | name: Discovery tests 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | branch: 7 | description: 'A branch name' 8 | required: false 9 | default: 'master' 10 | 11 | jobs: 12 | discovery-tests: 13 | runs-on: ubuntu-latest 14 | timeout-minutes: 360 15 | steps: 16 | - name: Check out the repo 17 | uses: actions/checkout@v2 18 | with: 19 | ref: ${{ github.event.inputs.branch }} 20 | 21 | - name: Build and run discovery tests 22 | run: RUST_BACKTRACE=1 cargo test --release -- --ignored discovery 23 | 24 | -------------------------------------------------------------------------------- /.github/workflows/experiments.yaml: -------------------------------------------------------------------------------- 1 | name: Experiment with configs 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | job: 7 | description: 'An experiment job name (collect-metrics or bench-scientific)' 8 | required: true 9 | 10 | config_url: 11 | description: 'An experiment config url' 12 | required: true 13 | 14 | jobs: 15 | collect-metrics: 16 | runs-on: ubuntu-latest 17 | if: ${{ github.event.inputs.job == 'collect-metrics' }} 18 | timeout-minutes: 360 19 | steps: 20 | - uses: actions/checkout@v2 21 | - uses: actions/setup-python@v2 22 | with: 23 | python-version: 3.8 24 | 25 | - uses: actions-rs/toolchain@v1 26 | with: 27 | toolchain: stable 28 | - run: python ./experiments/etc/collect-metrics.py ${{ github.event.inputs.config_url }} 29 | 30 | - name: Upload Results artifact 31 | uses: actions/upload-artifact@v4 32 | with: 33 | name: experiments-results.csv 34 | path: experiments/etc/collect-metrics-results.csv 35 | 36 | 37 | bench-scientific: 38 | runs-on: ubuntu-latest 39 | if: ${{ github.event.inputs.job == 'bench-scientific' }} 40 | timeout-minutes: 360 41 | steps: 42 | - uses: actions/checkout@v2 43 | - uses: actions/setup-python@v2 44 | with: 45 | python-version: 3.8 46 | 47 | - uses: actions-rs/toolchain@v1 48 | with: 49 | toolchain: stable 50 | - run: python ./experiments/etc/bench-scientific.py ${{ github.event.inputs.config_url }} 51 | 52 | - name: Upload Results artifact 53 | uses: actions/upload-artifact@v4 54 | with: 55 | name: bench-results 56 | path: experiments/etc/results 57 | 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | target 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | *Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | # IDE 13 | .idea 14 | *.iml 15 | .vscode 16 | 17 | # python experiments 18 | *.pyc 19 | __pycache__ 20 | .venv 21 | 22 | # ignore files from heuristic playground 23 | *.png 24 | heuristic_state.json 25 | 26 | # profiler 27 | callgrind* -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width=120 2 | use_small_heuristics="Max" 3 | # fn_single_line=true -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | message: "If you use this software, please cite it as below." 3 | authors: 4 | - family-names: "Builuk" 5 | given-names: "Ilya" 6 | orcid: "https://orcid.org/0000-0002-7613-7412" 7 | title: "Rosomaxa, Vehicle Routing Problem Solver" 8 | version: 1.25.0 9 | doi: 10.5281/zenodo.4624037 10 | date-released: 2024-07-13 11 | url: "https://github.com/reinterpretcat/vrp" 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | 4 | members = [ 5 | "rosomaxa", 6 | "vrp-core", 7 | "vrp-cli", 8 | "vrp-pragmatic", 9 | "vrp-scientific", 10 | 11 | "examples/json-pragmatic", 12 | "experiments/heuristic-research" 13 | ] 14 | 15 | [workspace.package] 16 | version = "1.25.0" 17 | authors = ["Ilya Builuk "] 18 | license = "Apache-2.0" 19 | keywords = ["vrp", "optimization"] 20 | categories = ["algorithms", "science"] 21 | readme = "README.md" 22 | homepage = "https://github.com/reinterpretcat/vrp" 23 | repository = "https://github.com/reinterpretcat/vrp" 24 | edition = "2024" 25 | 26 | [workspace.dependencies] 27 | # internal dependencies 28 | rosomaxa = { path = "rosomaxa", version = "0.9.0" } 29 | vrp-core = { path = "vrp-core", version = "1.25.0" } 30 | vrp-scientific = { path = "vrp-scientific", version = "1.25.0" } 31 | vrp-pragmatic = { path = "vrp-pragmatic", version = "1.25.0" } 32 | vrp-cli = { path = "vrp-cli", version = "1.25.0" } 33 | 34 | # external dependencies 35 | serde = { version = "1.0.219", features = ["derive"] } 36 | serde_json = "1.0.140" 37 | rand = { version = "0.8.5", features = ["small_rng"] } 38 | rayon = "1.10.0" 39 | rustc-hash = "2.1.1" 40 | paste = "1.0.15" 41 | lazy_static = "1.5.0" 42 | 43 | # dev dependencies 44 | criterion = "0.5.1" 45 | 46 | [profile.release] 47 | lto = "fat" # enables "fat" LTO, for faster release builds 48 | codegen-units = 1 # makes sure that all code is compiled together, for LTO 49 | 50 | [profile.test] 51 | opt-level = 1 # enables thin local LTO and some optimizations. 52 | 53 | [profile.bench] 54 | lto = "fat" # enables "fat" LTO, for faster becnhmark builds 55 | codegen-units = 1 # makes sure that all code is compiled together, for LTO 56 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:1.87-alpine AS Builder 2 | 3 | LABEL maintainer="Ilya Builuk " \ 4 | org.opencontainers.image.title="A Vehicle Routing Problem solver CLI" \ 5 | org.opencontainers.image.description="A tool to solve real world Vehicle Routing Problems" \ 6 | org.opencontainers.image.source="https://github.com/reinterpretcat/vrp" \ 7 | org.opencontainers.image.licenses="Apache-2.0" \ 8 | org.opencontainers.image.authors="Ilya Builuk " 9 | 10 | RUN apk add --no-cache musl-dev 11 | 12 | WORKDIR /src/ 13 | 14 | # copy source code 15 | COPY Cargo.toml ./ 16 | COPY experiments/heuristic-research ./experiments/heuristic-research 17 | COPY examples ./examples 18 | COPY rosomaxa ./rosomaxa 19 | COPY vrp-core ./vrp-core 20 | COPY vrp-scientific ./vrp-scientific 21 | COPY vrp-pragmatic ./vrp-pragmatic 22 | COPY vrp-cli ./vrp-cli 23 | 24 | RUN cargo build --release -p vrp-cli 25 | 26 | 27 | FROM alpine:3.18 28 | 29 | ENV SOLVER_DIR=/solver 30 | 31 | RUN mkdir $SOLVER_DIR 32 | COPY --from=Builder /src/target/release/vrp-cli $SOLVER_DIR/vrp-cli 33 | 34 | WORKDIR $SOLVER_DIR 35 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | -------------------------------------------------------------------------------- /docs/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Ilya Builuk "] 3 | description = "This book describes a Vehicle Routing Problem solver." 4 | language = "en" 5 | multilingual = false 6 | src = "src" 7 | title = "A Vehicle Routing Problem Solver Documentation" 8 | 9 | # cargo install mdbook-puml 10 | [preprocessor.puml] 11 | 12 | [output.html] 13 | default-theme = "navy" 14 | git-repository-url = "https://github.com/reinterpretcat/vrp/" 15 | additional-css = [ 16 | "resources/leaflet.css" 17 | ] 18 | additional-js = [ 19 | "resources/leaflet.js", 20 | "resources/map.js" 21 | ] 22 | 23 | [output.html.fold] 24 | enable = true 25 | level = 1 26 | -------------------------------------------------------------------------------- /docs/resources/map.js: -------------------------------------------------------------------------------- 1 | let geojson = document.getElementById("geojson"); 2 | 3 | if (geojson) { 4 | const map = L.map('map').setView([52.515, 13.413], 12); 5 | 6 | L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { 7 | maxZoom: 19, 8 | attribution: '© OpenStreetMap contributors' 9 | }).addTo(map); 10 | 11 | const geojsonLayer = L.geoJSON(JSON.parse(geojson.innerText), { 12 | weight: 2, 13 | style: function (feature) { 14 | return { 15 | color: feature.properties.stroke || (feature.properties["marker-symbol"] !== "marker" ? '#000000' : "#cccccc") 16 | }; 17 | }, 18 | pointToLayer: function (feature, latlng) { 19 | return L.circleMarker(latlng, { 20 | radius: (feature.properties["marker-symbol"] === "warehouse" ? 16 : 12), 21 | fillColor: feature.properties["marker-color"], 22 | fillOpacity: 0.75, 23 | weight: 2, 24 | }); 25 | }, 26 | onEachFeature: function (feature, layer) { 27 | const properties = Object.keys(feature.properties) 28 | .filter(key => !["marker-symbol", "marker-size", "marker-color", "stroke", "stroke-width"].includes(key)) 29 | .reduce((obj, key) => { 30 | obj[key] = feature.properties[key]; 31 | return obj; 32 | }, {}); 33 | 34 | layer.bindPopup('
' + JSON.stringify(properties, null, ' ')
35 |                 .replace(/[{}"]/g, '') + '
'); 36 | } 37 | }); 38 | 39 | geojsonLayer.addTo(map); 40 | map.fitBounds(geojsonLayer.getBounds()); 41 | } 42 | -------------------------------------------------------------------------------- /docs/resources/vrp-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reinterpretcat/vrp/fdf596a50af40950aec9c27bf4f919393919c69d/docs/resources/vrp-example.png -------------------------------------------------------------------------------- /docs/src/concepts/index.md: -------------------------------------------------------------------------------- 1 | # Concepts 2 | 3 | This section describes the ways how VRP problem can be specified in order to run the solver. At the moment, there are 4 | two options: 5 | 6 | * **pragmatic**: a json format used to model a real world VRP problems 7 | * **scientific** a set of text formats used to benchmark various algorithms in scientific literature. 8 | -------------------------------------------------------------------------------- /docs/src/concepts/pragmatic/index.md: -------------------------------------------------------------------------------- 1 | # Pragmatic 2 | 3 | Pragmatic format aims to model a multiple VRP variants through single problem and solution model schemas which are 4 | described in details in next sections. 5 | 6 | 7 | ## Performance 8 | 9 | There is no limit on problem size, solver should be able to solve problems with thousands of jobs in fairly reasonable 10 | amount of time depending on your termination criteria (e.g. time or amount of iterations/generations). However, exact 11 | performance depends on VRP variant (e.g. _VRPPD_ is slower than _CVRPTW_). 12 | 13 | 14 | ## Quality of results 15 | 16 | Although results seems to be comparable with alternative solutions, default metaheuristic still can be improved. 17 | 18 | 19 | ## Examples 20 | 21 | A various examples can be found in [pragmatic examples section](../../examples/pragmatic/index.md). -------------------------------------------------------------------------------- /docs/src/concepts/pragmatic/problem/resources.md: -------------------------------------------------------------------------------- 1 | # Shared resources 2 | 3 | A `fleet.resources` specifies an optional section which goal is to control distribution of limited shared resource 4 | between different vehicles. 5 | 6 | 7 | ## Reload resource 8 | 9 | An idea of reload resource is to put limit on amount of deliveries in total loaded to the multiple vehicles on specific 10 | reload place. A good example is some warehouse which can be visited by multiple vehicles in the middle of their tours, 11 | but it has only limited amount of deliveries. 12 | 13 | The reload resource definition has the following properties: 14 | 15 | - `type` (required): should be set to `reload` 16 | - `id` (required): an unique resource id. Put this id in vehicle reload's `resourceId` property to trigger shared resource behavior 17 | - `capacity` (required): total amount of resource. It has the same type as vehicle's `capacity` property. 18 | 19 | An example of a reload resource definition: 20 | 21 | ```json 22 | {{#include ../../../../../examples/data/pragmatic/basics/reload.resource.problem.json:174:180}} 23 | ``` 24 | 25 | An example of a vehicle reload with a reference to the resource definition: 26 | 27 | ```json 28 | {{#include ../../../../../examples/data/pragmatic/basics/reload.resource.problem.json:152:161}} 29 | ``` 30 | 31 | The full example can be found [here](../../../examples/pragmatic/basics/reload.md#Shared-reload-resource). -------------------------------------------------------------------------------- /docs/src/concepts/pragmatic/routing/index.md: -------------------------------------------------------------------------------- 1 | # Routing data 2 | 3 | In order to solve real life VRP, you need to provide routing information, such as distances and durations between all 4 | locations in the problem. Getting this data is not a part of the solver, you need to use some external service to get it. 5 | Once received, it has to be passed within VRP definition in specific routing matrix format. 6 | 7 | When no routing matrix information supplied, the solver uses haversine distance approximation. See more information 8 | about such behavior [here](../../../getting-started/routing.md). 9 | 10 | 11 | ## Location format 12 | 13 | Location can be represented as one of two types: 14 | 15 | * location as geocoodinate 16 | 17 | ```json 18 | {{#include ../../../../../examples/data/pragmatic/simple.basic.problem.json:10:13}} 19 | ``` 20 | 21 | * location as index reference in routing matrix 22 | 23 | ```json 24 | {{#include ../../../../../examples/data/pragmatic/simple.index.problem.json:10:12}} 25 | ``` 26 | 27 | Please note, that you cannot mix these types in one problem definition. Also routing approximation cannot be used with 28 | location indices. 29 | 30 | 31 | ## Related errors 32 | 33 | * [E0002 cannot create transport costs](../errors/index.md#e0002) 34 | * [E1500 duplicate profile names](../errors/index.md#e1500) 35 | * [E1501 empty profile collection](../errors/index.md#e1501) 36 | * [E1502 mixing different location types](../errors/index.md#e1502) 37 | * [E1503 location indices requires routing matrix to be specified](../errors/index.md#e1503) 38 | * [E1504 amount of locations does not match matrix dimension](../errors/index.md#e1504) 39 | * [E1505 unknown matrix profile name in vehicle or vicinity clustering profile](../errors/index.md#e1505) 40 | -------------------------------------------------------------------------------- /docs/src/concepts/pragmatic/routing/profile.md: -------------------------------------------------------------------------------- 1 | # Routing matrix profiles 2 | 3 | In order to solve VRP, you need to specify at least one routing matrix profile. 4 | 5 | 6 | ## Usage 7 | 8 | Routing matrix profiles are defined in `fleet.profiles`: 9 | 10 | ```json 11 | {{#include ../../../../../examples/data/pragmatic/simple.basic.problem.json:135:139}} 12 | ``` 13 | 14 | The `name` must be unique for each matrix profile and it should referenced by `profile.matrix` property defined on vehicle: 15 | 16 | ```json 17 | {{#include ../../../../../examples/data/pragmatic/simple.basic.problem.json:104:106}} 18 | ``` 19 | 20 | Use `-m` option to pass the matrix: 21 | 22 | vrp-cli solve pragmatic problem.json -m routing_matrix.json -o solution.json 23 | 24 | If you don't pass any routing matrix, then [haversine formula](https://en.wikipedia.org/wiki/Haversine_formula) is used to 25 | calculate distances between geo locations. Durations are calculated using speed value defined via `speed` property in 26 | each profile. It is optional, default value is `10` which corresponds to `10m/s`. 27 | 28 | 29 | ## Multiple profiles 30 | 31 | In general, you're not limited to one single routing profile. You can define multiple ones and pass their matrices 32 | to the solver: 33 | 34 | vrp-cli solve pragmatic problem.json -m routing_matrix_car.json -m routing_matrix_truck.json 35 | 36 | Make sure that for all profile names in `fleet.profiles` you have the corresponding matrix specified. 37 | 38 | See [multiple profiles example](../../../examples/pragmatic/basics/profiles.md). 39 | 40 | 41 | ## Time dependent routing 42 | 43 | In order to use this feature, specify more than one routing matrix for each profile with timestamp property set. 44 | 45 | -------------------------------------------------------------------------------- /docs/src/concepts/pragmatic/solution/index.md: -------------------------------------------------------------------------------- 1 | # Pragmatic solution 2 | 3 | A pragmatic solution is a result of metaheuristic work and, essentially, consists of three main parts: 4 | 5 | * statistic 6 | * list of tours 7 | * list of unassigned jobs 8 | -------------------------------------------------------------------------------- /docs/src/concepts/pragmatic/solution/statistic.md: -------------------------------------------------------------------------------- 1 | # Statistic 2 | 3 | A statistic entity represents total statistic for the whole solution or one tour. It has the following structure: 4 | 5 | * **cost**: a cost in abstract units 6 | * **distance**: a total distance in distance units 7 | * **duration**: a total duration in duration units 8 | * **times**: a duration split into specific groups: 9 | * **driving**: a total driving duration 10 | * **serving**: a total serving jobs duration 11 | * **waiting**: a total waiting time for time windows 12 | * **break**: a total break duration 13 | * **commuting**: a total commute duration (used only by vicinity clustering) 14 | * **parking**: a total parking time (used only by vicinity clustering) 15 | 16 | 17 | A solution statistic example: 18 | 19 | ```json 20 | {{#include ../../../../../examples/data/pragmatic/simple.basic.solution.json:2:14}} 21 | ``` -------------------------------------------------------------------------------- /docs/src/concepts/pragmatic/solution/violations.md: -------------------------------------------------------------------------------- 1 | # Violations 2 | 3 | Some of the constraints, specified by the problem, are considered as soft and can be violated under certain circumstances. 4 | Violations are listed in `violations` collection and divided in to specific groups. 5 | 6 | 7 | ## Vehicle Break violation 8 | 9 | A vehicle break is considered as soft constraint and can be violated if the solver is not able to assign it. When it is 10 | violated, the following object is returned: 11 | 12 | ```json 13 | { 14 | "type": "break", 15 | "vehicleId": "my_vehicle_id", 16 | "shiftIndex": 0 17 | } 18 | ``` -------------------------------------------------------------------------------- /docs/src/concepts/scientific/index.md: -------------------------------------------------------------------------------- 1 | # Scientific formats 2 | 3 | The project supports two text formats widely used for benchmarking various a algorithms in scientific papers: 4 | 5 | - **Solomon**: specifies CVRPTW 6 | - **Li&Lim**: specifies VRPPD 7 | - **tsplib** specifies CVRPTW -------------------------------------------------------------------------------- /docs/src/concepts/scientific/lilim.md: -------------------------------------------------------------------------------- 1 | # Li&Lim 2 | 3 | To run the problem from Li&Lim set, simply specify _lilim_ as a type: 4 | 5 | vrp-cli solve lilim LC1_10_2.txt -o LC1_10_2_solution.txt 6 | 7 | For details see [Li&Lim benchmark](https://www.sintef.no/projectweb/top/pdptw/li-lim-benchmark). -------------------------------------------------------------------------------- /docs/src/concepts/scientific/solomon.md: -------------------------------------------------------------------------------- 1 | # Solomon problems 2 | 3 | To run the problem from `solomon` set, simply specify _solomon_ as a type. The following command solves solomon problem 4 | defined in _RC1_10_1.txt_ and stores solution in _RC1_10_1_solution.txt_: 5 | 6 | vrp-cli solve solomon RC1_10_1.txt -o RC1_10_1_solution.txt 7 | 8 | Optionally, you can specify initial solution to start with: 9 | 10 | vrp-cli solve solomon RC1_10_1.txt --init-solution RC1_10_1_solution_initial.txt -o RC1_10_1_solution_improved.txt 11 | 12 | 13 | For details see [Solomon benchmark](https://www.sintef.no/projectweb/top/vrptw/solomon-benchmark). -------------------------------------------------------------------------------- /docs/src/concepts/scientific/tsplib.md: -------------------------------------------------------------------------------- 1 | # TSPLIB problems 2 | 3 | To run the problem from `tsplib` data set, simply specify _tsplib_ as a type. Please note, only few features of the 4 | format are supported. 5 | 6 | Some benchmarks can be found [here](http://vrp.atd-lab.inf.puc-rio.br/index.php/en/). -------------------------------------------------------------------------------- /docs/src/examples/index.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | This section contains data and code examples. 4 | -------------------------------------------------------------------------------- /docs/src/examples/interop/index.md: -------------------------------------------------------------------------------- 1 | # Programmatic usage 2 | 3 | This section contains examples which show how to call the solver from other languages. -------------------------------------------------------------------------------- /docs/src/examples/interop/java.md: -------------------------------------------------------------------------------- 1 | # Java 2 | 3 | This is example how to call solver methods from **java**. You need to make sure that `vrp-cli` library is available 4 | in runtime, e.g. by copying corresponding binary (`libvrp_cli.so` on Linux) to `resources` directory. To build it, use 5 | the following command: 6 | 7 | cargo build --release 8 | 9 | ```java 10 | {{#include ../../../../examples/jvm-interop/src/main/java/vrp/example/java/Application.java}} 11 | ``` 12 | 13 | You can check the project repository for complete example. -------------------------------------------------------------------------------- /docs/src/examples/interop/kotlin.md: -------------------------------------------------------------------------------- 1 | # Kotlin 2 | 3 | This is example how to call solver methods from **kotlin**. You need to make sure that `vrp-cli` library is available 4 | in runtime, e.g. by copying corresponding binary (`libvrp_cli.so` on Linux) to `resources` directory. To build it, use 5 | the following command; 6 | 7 | cargo build --release 8 | 9 | ```kotlin 10 | {{#include ../../../../examples/jvm-interop/src/main/kotlin/vrp/example/kotlin/Application.kt}} 11 | ``` 12 | 13 | You can check the project repository for complete example. -------------------------------------------------------------------------------- /docs/src/examples/pragmatic/basics/break.md: -------------------------------------------------------------------------------- 1 | # Vehicle break 2 | 3 | In general, there are two break types: optional and required. 4 | 5 | 6 | ## Optional break 7 | 8 | This example demonstrates how to use optional vehicle break with time window and omitted location. 9 | 10 |
11 | Problem

12 | 13 | ```json 14 | {{#include ../../../../../examples/data/pragmatic/basics/break.basic.problem.json}} 15 | ``` 16 | 17 |

18 | 19 |
20 | Solution

21 | 22 | ```json 23 | {{#include ../../../../../examples/data/pragmatic/basics/break.basic.solution.json}} 24 | ``` 25 | 26 |

27 | 28 | 29 | ## Required break (experimental) 30 | 31 | This example demonstrates how to use required vehicle break which has to be scheduled at specific time during travel 32 | between two stops. 33 | 34 |
35 | Problem

36 | 37 | ```json 38 | {{#include ../../../../../examples/data/pragmatic/basics/break.required.problem.json}} 39 | ``` 40 | 41 |

42 | 43 |
44 | Solution

45 | 46 | ```json 47 | {{#include ../../../../../examples/data/pragmatic/basics/break.required.solution.json}} 48 | ``` 49 | 50 |

51 | 52 | Please note, that departure rescheduling is disabled by setting `shift.start.earliest` equal to `shift.start.latest`. 53 | At the moment, this is a hard requirement when such break type is used. -------------------------------------------------------------------------------- /docs/src/examples/pragmatic/basics/index.md: -------------------------------------------------------------------------------- 1 | # Basic usage 2 | 3 | This section contains minimalistic examples to demonstrate how to use various features. -------------------------------------------------------------------------------- /docs/src/examples/pragmatic/basics/job-priorities.md: -------------------------------------------------------------------------------- 1 | # Job priorities 2 | 3 | There are two types of job priorities: 4 | 5 | * **assignment priority**: the solver tries to avoid such jobs to be unassigned by maximizing their total value. 6 | Assignment priority is modeled by _value_ property on the job and used within the _maximize-value_ objective. 7 | * **order priority**: the solver tries to assign such jobs prior others, close to beginning of the route. 8 | Order priority is modeled by _order_ property on the job. 9 | 10 | ## Basic job value example 11 | 12 | The example below demonstrates how to use assignment priority by defining _value_ on the jobs. The source problem has a 13 | single vehicle with limited capacity, therefore one job has to be unassigned. The solver is forced to skip the cheapest 14 | one as it has no value associated with it. 15 | 16 | Please note, that there is no need to redefine objective to include `maximize-value` one as it will be added automatically 17 | on top of default. 18 | 19 |
20 | Problem

21 | 22 | ```json 23 | {{#include ../../../../../examples/data/pragmatic/basics/priorities.value.problem.json}} 24 | ``` 25 | 26 |

27 | 28 |
29 | Solution

30 | 31 | ```json 32 | {{#include ../../../../../examples/data/pragmatic/basics/priorities.value.solution.json}} 33 | ``` 34 | 35 |

36 | 37 |
38 | 39 | 42 | 43 |
44 | -------------------------------------------------------------------------------- /docs/src/examples/pragmatic/basics/multi-day.md: -------------------------------------------------------------------------------- 1 | # Multi day/shift 2 | 3 | This example demonstrates how to simulate multi day/shift planning scenario. The problem has jobs with time windows of 4 | different days and one vehicle type with two shifts on different days. 5 | 6 | 7 |
8 | Problem

9 | 10 | ```json 11 | {{#include ../../../../../examples/data/pragmatic/basics/multi-day.basic.problem.json}} 12 | ``` 13 | 14 |

15 | 16 |
17 | Solution

18 | 19 | ```json 20 | {{#include ../../../../../examples/data/pragmatic/basics/multi-day.basic.solution.json}} 21 | ``` 22 | 23 |

24 | -------------------------------------------------------------------------------- /docs/src/examples/pragmatic/basics/profiles.md: -------------------------------------------------------------------------------- 1 | # Multiple routing profiles 2 | 3 | This example demonstrates how to use multiple routing profiles: `car` and `truck`. 4 | 5 | 6 |
7 | List of problem locations

8 | 9 | ```json 10 | {{#include ../../../../../examples/data/pragmatic/basics/profiles.basic.locations.json}} 11 | ``` 12 | 13 |

14 | 15 |
16 | Routing matrix for car

17 | 18 | ```json 19 | {{#include ../../../../../examples/data/pragmatic/basics/profiles.basic.matrix.car.json}} 20 | ``` 21 | 22 |

23 | 24 |
25 | Routing matrix for truck

26 | 27 | ```json 28 | {{#include ../../../../../examples/data/pragmatic/basics/profiles.basic.matrix.truck.json}} 29 | ``` 30 | 31 |

32 | 33 | 34 |
35 | Problem

36 | 37 | ```json 38 | {{#include ../../../../../examples/data/pragmatic/basics/profiles.basic.problem.json}} 39 | ``` 40 | 41 |

42 | 43 |
44 | Solution

45 | 46 | ```json 47 | {{#include ../../../../../examples/data/pragmatic/basics/profiles.basic.solution.json}} 48 | ``` 49 | 50 |

51 | 52 |
53 | Usage with cli

54 | 55 | ``` 56 | vrp-cli solve pragmatic profiles.basic.problem.json -m profiles.basic.matrix.car.json -m profiles.basic.matrix.truck -o profiles.basic.solution.json 57 | ``` 58 | 59 |

60 | -------------------------------------------------------------------------------- /docs/src/examples/pragmatic/basics/recharge.md: -------------------------------------------------------------------------------- 1 | # Recharge stations 2 | 3 | This example demonstrates an **experimental** feature to model a simple scenario of `Electric VRP`. Here, the vehicles have 4 | a distance limit (10km), so that they are forced to visit charging stations. Each station is defined by specific location, 5 | charging duration and time windows. 6 | 7 |
8 | Problem

9 | 10 | ```json 11 | {{#include ../../../../../examples/data/pragmatic/basics/recharge.basic.problem.json}} 12 | ``` 13 | 14 |

15 | 16 |
17 | Solution

18 | 19 | ```json 20 | {{#include ../../../../../examples/data/pragmatic/basics/recharge.basic.solution.json}} 21 | ``` 22 | 23 |

24 | 25 | 26 | 29 | 30 |
31 | 32 | **NOTE**: the feature is on early stage -------------------------------------------------------------------------------- /docs/src/examples/pragmatic/basics/relations.md: -------------------------------------------------------------------------------- 1 | # Relations 2 | 3 | These examples demonstrates how to use relation feature. 4 | 5 | 6 | ## Relation of Any type 7 | 8 | In this example, `any` relation locks two jobs to specific vehicle in any order. 9 | 10 |
11 | Problem

12 | 13 | ```json 14 | {{#include ../../../../../examples/data/pragmatic/basics/relation-any.basic.problem.json}} 15 | ``` 16 | 17 |

18 | 19 |
20 | Solution

21 | 22 | ```json 23 | {{#include ../../../../../examples/data/pragmatic/basics/relation-any.basic.solution.json}} 24 | ``` 25 | 26 |

27 | 28 | 29 | ## Relation of Strict type 30 | 31 | In this example, `strict` relation locks two jobs to specific vehicle starting from departure. 32 | 33 |
34 | Problem

35 | 36 | ```json 37 | {{#include ../../../../../examples/data/pragmatic/basics/relation-strict.basic.problem.json}} 38 | ``` 39 | 40 |

41 | 42 |
43 | Solution

44 | 45 | ```json 46 | {{#include ../../../../../examples/data/pragmatic/basics/relation-strict.basic.solution.json}} 47 | ``` 48 | 49 |

50 | -------------------------------------------------------------------------------- /docs/src/examples/pragmatic/basics/skills.md: -------------------------------------------------------------------------------- 1 | # Skills 2 | 3 | This example demonstrates how to use the skills feature with jobs and vehicles. In general, the skills feature is useful 4 | for locking specific jobs to specific vehicles. 5 | 6 | 7 |
8 | Complete problem json

9 | 10 | ```json 11 | {{#include ../../../../../examples/data/pragmatic/basics/skills.basic.problem.json}} 12 | ``` 13 | 14 |

15 | 16 |
17 | Complete solution json

18 | 19 | ```json 20 | {{#include ../../../../../examples/data/pragmatic/basics/skills.basic.solution.json}} 21 | ``` 22 | 23 |

24 | -------------------------------------------------------------------------------- /docs/src/examples/pragmatic/basics/unassigned.md: -------------------------------------------------------------------------------- 1 | # Unassigned jobs 2 | 3 | This example demonstrates one job which is unassigned due to time window constraints. 4 | 5 |
6 | Problem

7 | 8 | ```json 9 | {{#include ../../../../../examples/data/pragmatic/basics/unassigned.unreachable.problem.json}} 10 | ``` 11 | 12 |

13 | 14 |
15 | Solution

16 | 17 | ```json 18 | {{#include ../../../../../examples/data/pragmatic/basics/unassigned.unreachable.solution.json}} 19 | ``` 20 | 21 |

22 | -------------------------------------------------------------------------------- /docs/src/examples/pragmatic/clustering/index.md: -------------------------------------------------------------------------------- 1 | # Clustering examples 2 | 3 | The general idea of the clustering feature is to make more realistic ETAs for the same stop jobs, even if their locations 4 | are not exactly the same. This section provides a few examples. 5 | -------------------------------------------------------------------------------- /docs/src/examples/pragmatic/clustering/vicinity-continue.md: -------------------------------------------------------------------------------- 1 | # Vicinity clustering with job continuation 2 | 3 | This examples demonstrates a `continue` type of visit: jobs are visited one by one with returning to the stop point in 4 | the end. 5 | 6 |
7 | Problem

8 | 9 | ```json 10 | {{#include ../../../../../examples/data/pragmatic/clustering/berlin.vicinity-continue.problem.json}} 11 | ``` 12 | 13 |

14 | 15 |
16 | Solution

17 | 18 | ```json 19 | {{#include ../../../../../examples/data/pragmatic/clustering/berlin.vicinity-continue.solution.json}} 20 | ``` 21 | 22 |

23 | 24 |
25 | 26 | 27 | 30 | 31 |
32 | 33 | As parking time is specified in clustering settings, each stop with clustered job has an extra `parking` property: 34 | 35 | ```json 36 | {{#include ../../../../../examples/data/pragmatic/clustering/berlin.vicinity-continue.solution.json:54:57}} 37 | ``` 38 | 39 | Here, two minutes are planned for parking car at the stop location. 40 | 41 | Activities in such stops have `commute` property which contains information about time, location, distance of commute trip. 42 | The property is split into `forward` and `backward` parts: 43 | 44 | ```json 45 | {{#include ../../../../../examples/data/pragmatic/clustering/berlin.vicinity-continue.solution.json:122:156}} 46 | ``` 47 | 48 | Here `forward` specifies information how to reach activity and `backward` - how to get back to the stop after the job is 49 | served. Original `time` on activity specifies actual service time. -------------------------------------------------------------------------------- /docs/src/examples/pragmatic/clustering/vicinity-return.md: -------------------------------------------------------------------------------- 1 | # Vicinity clustering with return 2 | 3 | This examples demonstrates a `return` type of visit: after each job visit, the driver has to return to the stop point. 4 | Check previous example for other details. 5 | 6 |
7 | Problem

8 | 9 | ```json 10 | {{#include ../../../../../examples/data/pragmatic/clustering/berlin.vicinity-return.problem.json}} 11 | ``` 12 | 13 |

14 | 15 |
16 | Solution

17 | 18 | ```json 19 | {{#include ../../../../../examples/data/pragmatic/clustering/berlin.vicinity-return.solution.json}} 20 | ``` 21 | 22 |

23 | 24 |
25 | 26 | 27 | 30 | 31 |
-------------------------------------------------------------------------------- /docs/src/examples/pragmatic/index.md: -------------------------------------------------------------------------------- 1 | # Pragmatic examples 2 | 3 | Here you can find multiple examples how to use different features such as break, reload, multi job, etc. 4 | 5 | Each example consists of: 6 | 7 | * __problem definition__: a json file with complete problem definition. 8 | * __solution__: a json file with one of the possible solutions 9 | 10 | For selected examples: 11 | 12 | * __geojson__ visualization with `leaflet` 13 | * __ordered list of unique locations__: a json file with list of locations in specific order which can be used to 14 | request a routing matrix. More info can be found [here](../../concepts/pragmatic/routing/format.md) 15 | * __routing matrix__: a json file with routing matrix described [here](../../concepts/pragmatic/routing/index.md) 16 | * __CLI command__ command line which can be used to reproduce results locally using `vrp-cli` tool. Original data can be 17 | found in `examples` folder or copy-pasted from example pages and stored locally. 18 | -------------------------------------------------------------------------------- /docs/src/examples/pragmatic/objectives/index.md: -------------------------------------------------------------------------------- 1 | # Objective examples 2 | 3 | Examples in this section demonstrate how different objectives affect final solution. All of them use the same problem 4 | variant with different objectives. 5 | 6 | Essentially, the problem definition has the following parameters: 7 | 8 | * `50` deliveries with demand `1` 9 | * `5` vehicles with capacity `20` 10 | * fixed vehicle cost is `20` 11 | * no time windows 12 | 13 | Information regarding objective schema can be found [here](../../../concepts/pragmatic/problem/objectives.md). 14 | -------------------------------------------------------------------------------- /docs/src/examples/pragmatic/objectives/objective-balance-activities.md: -------------------------------------------------------------------------------- 1 | # Balance activities with fleet minimization 2 | 3 |
4 | Problem

5 | 6 | ```json 7 | {{#include ../../../../../examples/data/pragmatic/objectives/berlin.balance-activities.problem.json}} 8 | ``` 9 | 10 |

11 | 12 |
13 | Solution

14 | 15 | ```json 16 | {{#include ../../../../../examples/data/pragmatic/objectives/berlin.balance-activities.solution.json}} 17 | ``` 18 | 19 |

20 | 21 |
22 | 23 | 26 | 27 |
28 | 29 | This objective balances amount of activities and minimizes fleet usage at the same time: 30 | 31 | ```json 32 | {{#include ../../../../../examples/data/pragmatic/objectives/berlin.balance-activities.problem.json:1004:1025}} 33 | ``` 34 | 35 | Only three vehicles used approximately 16 jobs per vehicle. If you remove `minimize-tours`, results should be similar 36 | to results on previous page. -------------------------------------------------------------------------------- /docs/src/examples/pragmatic/objectives/objective-balance-distance.md: -------------------------------------------------------------------------------- 1 | # Balance travelled distance 2 | 3 |
4 | Problem

5 | 6 | ```json 7 | {{#include ../../../../../examples/data/pragmatic/objectives/berlin.balance-distance.problem.json}} 8 | ``` 9 | 10 |

11 | 12 |
13 | Solution

14 | 15 | ```json 16 | {{#include ../../../../../examples/data/pragmatic/objectives/berlin.balance-distance.solution.json}} 17 | ``` 18 | 19 |

20 | 21 |
22 | 23 | 26 | 27 |
28 | 29 | This objective balances tour distances for all tours: 30 | 31 | ```json 32 | {{#include ../../../../../examples/data/pragmatic/objectives/berlin.balance-distance.problem.json:1004:1025}} 33 | ``` 34 | 35 | All used vehicles should have total tour distance close to each other. 36 | 37 | The same way you can balance by travel duration using `balance-duration` objective. -------------------------------------------------------------------------------- /docs/src/examples/pragmatic/objectives/objective-balance-max-load.md: -------------------------------------------------------------------------------- 1 | # Balance max load 2 | 3 |
4 | Problem

5 | 6 | ```json 7 | {{#include ../../../../../examples/data/pragmatic/objectives/berlin.balance-max-load.problem.json}} 8 | ``` 9 | 10 |

11 | 12 |
13 | Solution

14 | 15 | ```json 16 | {{#include ../../../../../examples/data/pragmatic/objectives/berlin.balance-max-load.solution.json}} 17 | ``` 18 | 19 |

20 | 21 |
22 | 23 | 26 | 27 |
28 | 29 | This objective balances max load across vehicles: 30 | 31 | ```json 32 | {{#include ../../../../../examples/data/pragmatic/objectives/berlin.balance-max-load.problem.json:1004:1025}} 33 | ``` 34 | 35 | As `minimize-tours` objective is not set, all available vehicles are used serving `10` jobs per vehicle. Result total 36 | cost is higher than for default objective. 37 | -------------------------------------------------------------------------------- /docs/src/examples/pragmatic/objectives/objective-default.md: -------------------------------------------------------------------------------- 1 | # Default behavior: fleet and cost minimization 2 | 3 |
4 | Problem

5 | 6 | ```json 7 | {{#include ../../../../../examples/data/pragmatic/objectives/berlin.default.problem.json}} 8 | ``` 9 | 10 |

11 | 12 |
13 | Solution

14 | 15 | ```json 16 | {{#include ../../../../../examples/data/pragmatic/objectives/berlin.default.solution.json}} 17 | ``` 18 | 19 |

20 | 21 |
22 | 23 | 26 | 27 |
28 | 29 | By default, the first objective for the solver is to minimize amount of unassigned jobs, then fleet usage, and the last 30 | is total cost minimization: 31 | 32 | ```json 33 | {{#include ../../../../../examples/data/pragmatic/objectives/berlin.default.problem.json:1004:1014}} 34 | ``` 35 | 36 | As result, solution has minimum amount of vehicles used to serve all jobs (`3`). 37 | 38 | Note, that load between these vehicles is not equally distributed as it increases the total cost. -------------------------------------------------------------------------------- /docs/src/getting-started/analysis.md: -------------------------------------------------------------------------------- 1 | # Analyzing results 2 | 3 | In this example, solution is returned in a `pragmatic` format which model is described in details 4 | [here](../concepts/pragmatic/solution/index.md). However, analyzing VRP solution might be a difficult task. That's why 5 | `pragmatic` format supports output in [geojson](https://en.wikipedia.org/wiki/GeoJSON) format which can be simply 6 | visualized in numerous web based front ends, e.g. [geojson.io](http://geojson.io/) or using open source tools such 7 | as `leaflet`: 8 | 9 | 12 | 13 |
14 | 15 | To return solution in `geojson` format, use extra `-g` or `--geo-json` option. 16 | 17 | ## Jupyter notebooks 18 | 19 | You might want to look at [this project](https://github.com/reinterpretcat/vrp-analysis). 20 | It provides some scripts and jupyter notebooks in order to perform deeper analysis of algorithm behaviour. 21 | -------------------------------------------------------------------------------- /docs/src/getting-started/index.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | This chapter describes basic information regarding the project and gives some instructions how to start with it. 4 | 5 | Alternative, more example based, tutorial can be found in dedicated [jupyter notebook](https://github.com/reinterpretcat/vrp/tree/master/examples/python-interop/tutorial.ipynb). 6 | -------------------------------------------------------------------------------- /docs/src/getting-started/routing.md: -------------------------------------------------------------------------------- 1 | # Acquiring routing info 2 | 3 | Once the problem is represented in `pragmatic` format, it's time to get matrix routing data. 4 | 5 | 6 | ## Routing locations 7 | 8 | The solver does not provide routing functionality, that's why you need to get it manually using unique locations from the 9 | problem definition. The process of getting locations for matrix routing and its usage is described 10 | [here](../concepts/pragmatic/routing/format.md). 11 | 12 | 13 | ## Routing matrix approximation 14 | 15 | For quick prototyping, `pragmatic` format supports distance approximation using [haversine formula](https://en.wikipedia.org/wiki/Haversine_formula) 16 | within fixed speed for durations. This helps you quickly check how solver works on specific problem variant without 17 | need to acquire routing matrix. 18 | 19 | The speed is `10m/s` by default and can be tweaked by setting optional `speed` property in a each profile separately. 20 | 21 | To use this feature, simply do not pass any matrix by omitting `-m` parameter. 22 | -------------------------------------------------------------------------------- /docs/src/images/rosomaxa.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reinterpretcat/vrp/fdf596a50af40950aec9c27bf4f919393919c69d/docs/src/images/rosomaxa.gif -------------------------------------------------------------------------------- /docs/src/index.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This project is about solving Vehicle Routing Problem which is common task in transportation planning and logistics. 4 | 5 | ## Vehicle Routing Problem 6 | From wiki: 7 | > The vehicle routing problem (VRP) is a combinatorial optimization and integer programming problem 8 | > which asks "What is the optimal set of routes for a fleet of vehicles to traverse in order to 9 | > deliver to a given set of customers?". It generalises the well-known travelling salesman problem 10 | > (TSP). 11 | > 12 | > Determining the optimal solution to VRP is NP-hard, so the size of problems that can be solved, 13 | > optimally, using mathematical programming or combinatorial optimization may be limited. 14 | > Therefore, commercial solvers tend to use heuristics due to the size and frequency of real 15 | > world VRPs they need to solve. 16 | 17 | 20 | 21 |
22 | 23 | ## Design 24 | 25 | Although performance is constantly in focus, a main idea behind projects' design is extensibility: 26 | the project aims to support a very wide range of VRP variations known as Rich VRP. This is achieved 27 | through various extension points: custom constraints, objective functions, acceptance criteria, etc. 28 | 29 | -------------------------------------------------------------------------------- /docs/src/internals/algorithms/index.md: -------------------------------------------------------------------------------- 1 | # Algorithms 2 | 3 | This chapter describes some used algorithms. 4 | 5 | 6 | ## References 7 | 8 | An incomplete list of important references: 9 | 10 | - Clarke, G & Wright, JW 1964: `Scheduling of vehicles from a Central Depot to a Number of the Delivery Point. Operations Research, 12 (4): 568-581` 11 | - Pisinger, David; Røpke, Stefan: `A general heuristic for vehicle routing problems` 12 | - Schrimpf, G., Schneider, K., Stamm-Wilbrandt, H., Dueck, V.: `Record Breaking Optimization Results Using the Ruin and Recreate Principle. J. of Computational Physics 159 (2000) 139–171` 13 | - Jan Christiaens, Greet Vanden Berghe: `Slack Induction by String Removals for Vehicle Routing Problems` 14 | - Thibaut Vidal: `Hybrid Genetic Search for the CVRP: Open-Source Implementation and SWAP* Neighborhood` 15 | - Richard F. Hartl, Thibaut Vidal: `Workload Equity in Vehicle Routing Problems: A Survey and Analysis` 16 | 17 | - Damminda Alahakoon, Saman K Halgamuge, Srinivasan Bala: `Dynamic self-organizing maps with controlled growth for knowledge discovery` 18 | - Daniel J. Russo, Benjamin Van Roy, Abbas Kazerouni, Ian Osband and Zheng Wen: `A Tutorial on Thompson Sampling` https://web.stanford.edu/~bvr/pubs/TS_Tutorial.pdf 19 | 20 | - Florian Arnold, Kenneth Sörensen: `What makes a solution good? The generation of problem-specific knowledge for heuristics` 21 | - Flavien Lucas, Romain Billot, Marc Sevaux: `A comment on "what makes a VRP solution good? The generation of problem-specific knowledge for heuristics"` 22 | - Erik Pitzer, Michael Affenzeller: `A Comprehensive Survey on Fitness Landscape Analysis` -------------------------------------------------------------------------------- /docs/src/internals/development/index.md: -------------------------------------------------------------------------------- 1 | # Development 2 | 3 | This chapter describes development topics, such as high level architecture, project structure, code style, testing, etc. 4 | -------------------------------------------------------------------------------- /docs/src/internals/index.md: -------------------------------------------------------------------------------- 1 | # Internals 2 | 3 | This chapter describes some internals of the project. 4 | 5 | ## Project structure 6 | 7 | The project consists of the following parts: 8 | 9 | - **vrp solver code**: the source code of the solver is split into four crates: 10 | - *rosomaxa*: a crate with various metaheuristic building blocks and its default implementation 11 | - *vrp-core*: a core crate for vrp domain 12 | - *vrp-scientific*: a crate with functionality to solve problems from some of scientific benchmarks on top of the core crate 13 | - *vrp-pragmatic*: a crate which provides logic to solve rich VRP using `pragmatic` json format on top of the core crate 14 | - *vrp-cli*: a crate which aggregates logic of others crates and exposes them as a library and application 15 | - **docs**: a source code of the user guide documentation published [here](https://reinterpretcat.github.io/vrp). 16 | Use [mdbook](https://github.com/rust-lang/mdBook) tool to build it locally. 17 | - **examples**: provides various examples: 18 | - *data*: a data examples such as problem definition, configuration, etc. 19 | - *json-pragmatic*: an example how to solve problem in `pragmatic` json format from rust code using the project crates 20 | - *jvm-interop*: a gradle project which demonstrates how to use the library from java and kotlin 21 | - **experiments**: a playground for various experiments 22 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # High-level examples 2 | 3 | This folder contains some high level examples. 4 | 5 | Check `vrp-core/examples` for Rust specific code examples. -------------------------------------------------------------------------------- /examples/data/config/config.telemetry.json: -------------------------------------------------------------------------------- 1 | { 2 | "termination": { 3 | "maxTime": 300 4 | }, 5 | "telemetry": { 6 | "progress": { 7 | "enabled": true, 8 | "logBest": 100, 9 | "logPopulation": 1000 10 | }, 11 | "metrics": { 12 | "enabled": true, 13 | "trackPopulation": 1000 14 | } 15 | }, 16 | "environment": { 17 | "logging": { 18 | "enabled": true 19 | }, 20 | "isExperimental": false 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/data/csv/jobs.csv: -------------------------------------------------------------------------------- 1 | ID,LAT,LNG,DEMAND,DURATION,TW_START,TW_END 2 | job1,52.52599,13.45413,2,5,2020-07-04T08:00:00Z,2020-07-04T12:00:00Z 3 | job2,52.5225,13.4095,1,3,, 4 | job2,52.5165,13.3808,-1,3,, 5 | job3,52.5316,13.3884,-3,5,2020-07-04T08:00:00Z,2020-07-04T16:00:00Z -------------------------------------------------------------------------------- /examples/data/csv/vehicles.csv: -------------------------------------------------------------------------------- 1 | ID,LAT,LNG,CAPACITY,TW_START,TW_END,AMOUNT,PROFILE 2 | vehicle1,52.4664,13.4023,40,2020-07-04T08:00:00Z,2020-07-04T20:00:00Z,10,car 3 | vehicle2,52.4959,13.3539,50,2020-07-04T08:00:00Z,2020-07-04T20:00:00Z,20,truck -------------------------------------------------------------------------------- /examples/data/pragmatic/basics/break.required.problem.json: -------------------------------------------------------------------------------- 1 | { 2 | "plan": { 3 | "jobs": [ 4 | { 5 | "id": "job1", 6 | "deliveries": [ 7 | { 8 | "places": [ 9 | { 10 | "location": { 11 | "lat": 51.06251, 12 | "lng": 13.72133 13 | }, 14 | "duration": 300 15 | } 16 | ], 17 | "demand": [ 18 | 1 19 | ] 20 | } 21 | ] 22 | } 23 | ] 24 | }, 25 | "fleet": { 26 | "vehicles": [ 27 | { 28 | "typeId": "vehicle", 29 | "vehicleIds": [ 30 | "vehicle_1" 31 | ], 32 | "profile": { 33 | "matrix": "normal_car" 34 | }, 35 | "costs": { 36 | "fixed": 22, 37 | "distance": 0.0002, 38 | "time": 0.005 39 | }, 40 | "shifts": [ 41 | { 42 | "start": { 43 | "earliest": "2019-07-04T09:00:00Z", 44 | "latest": "2019-07-04T09:00:00Z", 45 | "location": { 46 | "lat": 52.5316, 47 | "lng": 13.3884 48 | } 49 | }, 50 | "breaks": [ 51 | { 52 | "time": { 53 | "earliest": 7200, 54 | "latest": 7200 55 | }, 56 | "duration": 1800 57 | } 58 | ] 59 | } 60 | ], 61 | "capacity": [ 62 | 10 63 | ] 64 | } 65 | ], 66 | "profiles": [ 67 | { 68 | "name": "normal_car" 69 | } 70 | ] 71 | } 72 | } -------------------------------------------------------------------------------- /examples/data/pragmatic/basics/profiles.basic.locations.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "lat": 52.52599, 4 | "lng": 13.45413 5 | }, 6 | { 7 | "lat": 52.5225, 8 | "lng": 13.4095 9 | }, 10 | { 11 | "lat": 52.5316, 12 | "lng": 13.3884 13 | } 14 | ] -------------------------------------------------------------------------------- /examples/data/pragmatic/basics/profiles.basic.matrix.car.json: -------------------------------------------------------------------------------- 1 | { 2 | "profile": "normal_car", 3 | "travelTimes": [ 4 | 0, 5 | 681, 6 | 905, 7 | 750, 8 | 0, 9 | 546, 10 | 891, 11 | 502, 12 | 0 13 | ], 14 | "distances": [ 15 | 0, 16 | 3840, 17 | 5283, 18 | 4696, 19 | 0, 20 | 2668, 21 | 5112, 22 | 2470, 23 | 0 24 | ] 25 | } -------------------------------------------------------------------------------- /examples/data/pragmatic/basics/profiles.basic.matrix.truck.json: -------------------------------------------------------------------------------- 1 | { 2 | "profile": "normal_truck", 3 | "travelTimes": [ 4 | 0, 5 | 906, 6 | 1218, 7 | 970, 8 | 0, 9 | 750, 10 | 1152, 11 | 644, 12 | 0 13 | ], 14 | "distances": [ 15 | 0, 16 | 3840, 17 | 5283, 18 | 4482, 19 | 0, 20 | 2768, 21 | 5112, 22 | 2357, 23 | 0 24 | ] 25 | } -------------------------------------------------------------------------------- /examples/data/pragmatic/simple.basic.locations.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "lat": 52.52599, 4 | "lng": 13.45413 5 | }, 6 | { 7 | "lat": 52.5225, 8 | "lng": 13.4095 9 | }, 10 | { 11 | "lat": 52.5165, 12 | "lng": 13.3808 13 | }, 14 | { 15 | "lat": 52.5316, 16 | "lng": 13.3884 17 | } 18 | ] -------------------------------------------------------------------------------- /examples/data/pragmatic/simple.basic.matrix.json: -------------------------------------------------------------------------------- 1 | { 2 | "profile": "normal_car", 3 | "travelTimes": [ 4 | 0, 5 | 609, 6 | 981, 7 | 906, 8 | 813, 9 | 0, 10 | 371, 11 | 590, 12 | 1055, 13 | 514, 14 | 0, 15 | 439, 16 | 948, 17 | 511, 18 | 463, 19 | 0 20 | ], 21 | "distances": [ 22 | 0, 23 | 3840, 24 | 5994, 25 | 5333, 26 | 4696, 27 | 0, 28 | 2154, 29 | 3226, 30 | 5763, 31 | 2674, 32 | 0, 33 | 2145, 34 | 5112, 35 | 2470, 36 | 2152, 37 | 0 38 | ] 39 | } -------------------------------------------------------------------------------- /examples/data/scientific/solomon/C101.100.best.txt: -------------------------------------------------------------------------------- 1 | Route 1 : 81 78 76 71 70 73 77 79 80 2 | Route 2 : 57 55 54 53 56 58 60 59 3 | Route 3 : 98 96 95 94 92 93 97 100 99 4 | Route 4 : 32 33 31 35 37 38 39 36 34 5 | Route 5 : 13 17 18 19 15 16 14 12 6 | Route 6 : 90 87 86 83 82 84 85 88 89 91 7 | Route 7 : 43 42 41 40 44 46 45 48 51 50 52 49 47 8 | Route 8 : 67 65 63 62 74 72 61 64 68 66 69 9 | Route 9 : 5 3 7 8 10 11 9 6 4 2 1 75 10 | Route 10 : 20 24 25 27 29 30 28 26 23 22 21 11 | Cost 828 -------------------------------------------------------------------------------- /examples/data/scientific/solomon/C101.100.partial.txt: -------------------------------------------------------------------------------- 1 | Route 1 : 81 78 76 80 2 | Route 2 : 57 55 54 53 56 58 60 59 3 | Route 3 : 98 96 95 94 92 93 97 100 99 4 | Route 4 : 32 33 31 35 37 38 39 36 34 5 | Route 5 : 13 17 18 19 15 16 14 12 6 | Route 6 : 90 87 86 83 89 91 7 | Route 7 : 43 42 41 40 48 51 50 52 49 47 8 | Route 8 : 67 65 63 62 74 72 61 64 68 66 69 9 | Route 9 : 5 3 7 8 10 11 9 6 4 2 1 75 10 | Route 10 : 20 24 30 28 26 23 22 21 11 | Cost 1000 12 | -------------------------------------------------------------------------------- /examples/data/scientific/tsplib/A-n32-k5.vrp: -------------------------------------------------------------------------------- 1 | NAME : A-n32-k5 2 | COMMENT : (Augerat et al, No of trucks: 5, Optimal value: 784) 3 | TYPE : CVRP 4 | DIMENSION : 32 5 | EDGE_WEIGHT_TYPE : EUC_2D 6 | CAPACITY : 100 7 | NODE_COORD_SECTION 8 | 1 82 76 9 | 2 96 44 10 | 3 50 5 11 | 4 49 8 12 | 5 13 7 13 | 6 29 89 14 | 7 58 30 15 | 8 84 39 16 | 9 14 24 17 | 10 2 39 18 | 11 3 82 19 | 12 5 10 20 | 13 98 52 21 | 14 84 25 22 | 15 61 59 23 | 16 1 65 24 | 17 88 51 25 | 18 91 2 26 | 19 19 32 27 | 20 93 3 28 | 21 50 93 29 | 22 98 14 30 | 23 5 42 31 | 24 42 9 32 | 25 61 62 33 | 26 9 97 34 | 27 80 55 35 | 28 57 69 36 | 29 23 15 37 | 30 20 70 38 | 31 85 60 39 | 32 98 5 40 | DEMAND_SECTION 41 | 1 0 42 | 2 19 43 | 3 21 44 | 4 6 45 | 5 19 46 | 6 7 47 | 7 12 48 | 8 16 49 | 9 6 50 | 10 16 51 | 11 8 52 | 12 14 53 | 13 21 54 | 14 16 55 | 15 3 56 | 16 22 57 | 17 18 58 | 18 19 59 | 19 1 60 | 20 24 61 | 21 8 62 | 22 12 63 | 23 4 64 | 24 8 65 | 25 24 66 | 26 24 67 | 27 2 68 | 28 20 69 | 29 15 70 | 30 2 71 | 31 14 72 | 32 9 73 | DEPOT_SECTION 74 | 1 75 | -1 76 | EOF 77 | -------------------------------------------------------------------------------- /examples/data/scientific/tsplib/example.txt: -------------------------------------------------------------------------------- 1 | NAME : toy.vrp 2 | COMMENT : toy instance 3 | TYPE : CVRP 4 | DIMENSION : 6 5 | EDGE_WEIGHT_TYPE : EUC_2D 6 | CAPACITY : 30 7 | NODE_COORD_SECTION 8 | 1 38 46 9 | 2 59 46 10 | 3 96 42 11 | 4 47 61 12 | 5 26 15 13 | 6 66 6 14 | DEMAND_SECTION 15 | 1 0 16 | 2 16 17 | 3 18 18 | 4 1 19 | 5 13 20 | 6 8 21 | DEPOT_SECTION 22 | 1 23 | -1 24 | EOF -------------------------------------------------------------------------------- /examples/json-pragmatic/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "json-pragmatic" 3 | description = "A code example for solving rich VRP using vrp crates" 4 | publish = false 5 | version.workspace = true 6 | authors.workspace = true 7 | license.workspace = true 8 | keywords.workspace = true 9 | categories.workspace = true 10 | readme.workspace = true 11 | homepage.workspace = true 12 | repository.workspace = true 13 | edition.workspace = true 14 | 15 | [dependencies] 16 | vrp-pragmatic.workspace = true 17 | -------------------------------------------------------------------------------- /examples/json-pragmatic/README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | An examples of **Vehicle Routing Problem** solver usage. 4 | 5 | Please check [the repository](https://github.com/reinterpretcat/vrp) for more details. -------------------------------------------------------------------------------- /examples/jvm-interop/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .gradle 3 | *.class 4 | *.iml 5 | *.DS_Store 6 | *.kotlintest 7 | build/ 8 | output/ 9 | out/ 10 | 11 | gradle* -------------------------------------------------------------------------------- /examples/jvm-interop/README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | This example shows how to use `pragmatic` library from java or kotlin. 4 | 5 | # Usage 6 | 7 | - Build `vrp-cli` crate 8 | - Copy build artifact to resources 9 | - Prepare problem and routing matrix files in `pragmatic` format 10 | - Run one of examples specifying paths to problem and matrix(-ces) files 11 | -------------------------------------------------------------------------------- /examples/jvm-interop/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.jetbrains.kotlin.jvm' version '1.3.61' 4 | } 5 | 6 | group 'jvm-example' 7 | version '1.0-SNAPSHOT' 8 | 9 | repositories { 10 | mavenCentral() 11 | } 12 | 13 | dependencies { 14 | implementation 'net.java.dev.jna:jna:5.2.0' 15 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" 16 | } 17 | -------------------------------------------------------------------------------- /examples/jvm-interop/src/main/resources/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reinterpretcat/vrp/fdf596a50af40950aec9c27bf4f919393919c69d/examples/jvm-interop/src/main/resources/.keep -------------------------------------------------------------------------------- /examples/python-interop/config_types.py: -------------------------------------------------------------------------------- 1 | # Contains semi-automatically generated non-complete model of config format. 2 | # Please refer to documentation to define a full model 3 | 4 | from __future__ import annotations 5 | from pydantic.dataclasses import dataclass 6 | from typing import Optional 7 | 8 | 9 | @dataclass 10 | class Telemetry: 11 | progress: Progress 12 | 13 | 14 | @dataclass 15 | class Progress: 16 | enabled: bool 17 | logBest: int 18 | logPopulation: int 19 | dumpPopulation: bool 20 | 21 | 22 | Telemetry.__pydantic_model__.update_forward_refs() 23 | 24 | 25 | @dataclass 26 | class Config: 27 | termination: Termination 28 | telemetry: Optional[Telemetry] = Telemetry( 29 | progress=Progress( 30 | enabled=True, 31 | logBest=100, 32 | logPopulation=1000, 33 | dumpPopulation=False 34 | ) 35 | ) 36 | environment: Optional[Environment] = None 37 | 38 | 39 | @dataclass 40 | class Termination: 41 | maxTime: Optional[int] = None 42 | maxGenerations: Optional[int] = None 43 | 44 | 45 | @dataclass 46 | class Logging: 47 | enabled: bool 48 | 49 | 50 | Logging.__pydantic_model__.update_forward_refs() 51 | 52 | 53 | @dataclass 54 | class Environment: 55 | logging: Logging = Logging(enabled=True) 56 | isExperimental: Optional[bool] = None 57 | 58 | 59 | Config.__pydantic_model__.update_forward_refs() 60 | Telemetry.__pydantic_model__.update_forward_refs() 61 | Termination.__pydantic_model__.update_forward_refs() 62 | Environment.__pydantic_model__.update_forward_refs() 63 | -------------------------------------------------------------------------------- /experiments/gpt/vrp_ai/solver.py: -------------------------------------------------------------------------------- 1 | from . instances import * 2 | from . encoder import * 3 | from . tokenizer import * 4 | from .gpt import * 5 | 6 | 7 | -------------------------------------------------------------------------------- /experiments/heuristic-research/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "heuristic-research" 3 | description = "A playground for heuristic research" 4 | publish = false 5 | version.workspace = true 6 | authors.workspace = true 7 | license.workspace = true 8 | keywords.workspace = true 9 | categories.workspace = true 10 | readme.workspace = true 11 | homepage.workspace = true 12 | repository.workspace = true 13 | edition.workspace = true 14 | 15 | [lib] 16 | crate-type = ["cdylib", "rlib"] 17 | 18 | [dependencies] 19 | rosomaxa.workspace = true 20 | vrp-scientific.workspace = true 21 | 22 | serde.workspace = true 23 | serde_json.workspace = true 24 | lazy_static.workspace = true 25 | 26 | # added features as it fails to compile without them on github CI 27 | plotters = { version = "0.3.7", features = ["ab_glyph", "fontconfig-dlopen"] } 28 | plotters-canvas = "0.3.1" 29 | itertools = "0.14.0" 30 | wasm-bindgen = "0.2.100" 31 | web-sys = { version = "0.3.77", features = ["HtmlCanvasElement", "console"] } 32 | -------------------------------------------------------------------------------- /experiments/heuristic-research/README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | This crate is, essentially, a heuristic research playground. 4 | 5 | 6 | ## Build 7 | 8 | ```bash 9 | wasm-pack build --target web 10 | 11 | # install web server (or use any other) 12 | cargo install basic-http-server 13 | basic-http-server 14 | 15 | # open http://127.0.0.1:4000/www/ 16 | ``` 17 | 18 | ## TODO 19 | 20 | - refactor html-css-js scripts to avoid duplication 21 | - add more insights from heuristic -------------------------------------------------------------------------------- /experiments/heuristic-research/src/solver/mod.rs: -------------------------------------------------------------------------------- 1 | mod proxies; 2 | pub use self::proxies::*; 3 | 4 | mod state; 5 | pub use self::state::*; 6 | 7 | mod vector; 8 | pub use self::vector::*; 9 | 10 | mod vrp; 11 | pub use self::vrp::*; 12 | 13 | use rosomaxa::population::*; 14 | use rosomaxa::prelude::*; 15 | use std::sync::Arc; 16 | 17 | /// Gets proxy population of given type. 18 | fn get_population( 19 | context: C, 20 | population_type: &str, 21 | objective: Arc, 22 | environment: Arc, 23 | selection_size: usize, 24 | ) -> Box + Send + Sync> 25 | where 26 | C: RosomaxaContext + 'static, 27 | O: HeuristicObjective + Alternative + 'static, 28 | S: RosomaxaSolution + 'static, 29 | { 30 | match population_type { 31 | "greedy" => Box::new(ProxyPopulation::new(Greedy::new(objective, 1, None))), 32 | "elitism" => { 33 | Box::new(ProxyPopulation::new(Elitism::new(objective, environment.random.clone(), 2, selection_size))) 34 | } 35 | "rosomaxa" => Box::new(ProxyPopulation::new( 36 | Rosomaxa::new(context, objective, environment, RosomaxaConfig::new_with_defaults(selection_size)) 37 | .expect("cannot create rosomaxa with default configuration"), 38 | )), 39 | _ => unreachable!(), 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /experiments/heuristic-research/src/solver/vrp/population.rs: -------------------------------------------------------------------------------- 1 | use crate::EXPERIMENT_DATA; 2 | use rosomaxa::example::FitnessFn; 3 | use rosomaxa::prelude::Float; 4 | use std::sync::Arc; 5 | 6 | /// Returns a fitness function which can be used to evaluate population state. 7 | /// It is not actual fitness function, but a wrapper around [crate::FootprintState]. 8 | pub fn get_population_fitness_fn(generation: usize) -> FitnessFn { 9 | EXPERIMENT_DATA 10 | .lock() 11 | .ok() 12 | .and_then(|data| data.on_generation.get(&generation).map(|(footprint, _)| footprint.clone())) 13 | .map(|footprint| { 14 | Arc::new(move |input: &[Float]| { 15 | if let &[from, to] = input { 16 | footprint.get(from as usize, to as usize) as Float 17 | } else { 18 | panic!("expected 2 input values which encode from/to edge weights"); 19 | } 20 | }) 21 | }) 22 | .expect("cannot get data from EXPERIMENT_DATA") 23 | } 24 | 25 | /// Returns a description of population state. 26 | pub fn get_population_desc(generation: usize) -> String { 27 | EXPERIMENT_DATA 28 | .lock() 29 | .ok() 30 | .and_then(|data| { 31 | data.on_generation.get(&generation).map(|(footprint, individuals)| { 32 | format!("total [{}], known edges [{}]", individuals.len(), footprint.desc()) 33 | }) 34 | }) 35 | .expect("cannot get data from EXPERIMENT_DATA") 36 | } 37 | -------------------------------------------------------------------------------- /experiments/heuristic-research/tests/unit/solver/vector/objectives_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | const FITNESS_EPSILON: Float = 1E-05; 4 | 5 | #[test] 6 | fn can_find_rosenbrock_optimum() { 7 | let rosenbrock_fn = get_fitness_fn_by_name("rosenbrock"); 8 | 9 | assert!((rosenbrock_fn)(&[1., 1.]).abs() < FITNESS_EPSILON); 10 | } 11 | 12 | #[test] 13 | fn can_find_rastrigin_optimum() { 14 | let rastrigin_fn = get_fitness_fn_by_name("rastrigin"); 15 | 16 | assert!((rastrigin_fn)(&[0., 0.]).abs() < FITNESS_EPSILON); 17 | } 18 | 19 | #[test] 20 | fn can_find_himmelblau_optimum() { 21 | let himmelblau_fn = get_fitness_fn_by_name("himmelblau"); 22 | 23 | assert!((himmelblau_fn)(&[3., 2.]).abs() < FITNESS_EPSILON); 24 | assert!((himmelblau_fn)(&[-2.805118, 3.131312]).abs() < FITNESS_EPSILON); 25 | assert!((himmelblau_fn)(&[-3.77931, -3.28318]).abs() < FITNESS_EPSILON); 26 | assert!((himmelblau_fn)(&[3.584428, -1.848126]).abs() < FITNESS_EPSILON); 27 | } 28 | 29 | #[test] 30 | fn can_find_ackley_optimum() { 31 | let ackley_fn = get_fitness_fn_by_name("ackley"); 32 | 33 | assert!((ackley_fn)(&[0., 0.]).abs() < FITNESS_EPSILON); 34 | } 35 | 36 | #[test] 37 | fn can_find_matyas_optimum() { 38 | let matyas_fn = get_fitness_fn_by_name("matyas"); 39 | 40 | assert!((matyas_fn)(&[0., 0.]).abs() < FITNESS_EPSILON); 41 | } 42 | -------------------------------------------------------------------------------- /experiments/heuristic-research/tests/unit/solver/vrp/vrp_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[test] 4 | fn can_solve_scientific_problem() { 5 | let problem = r#"NAME : SMALL 6 | COMMENT : Test problem 7 | TYPE : CVRP 8 | DIMENSION : 5 9 | EDGE_WEIGHT_TYPE : EUC_2D 10 | CAPACITY : 100 11 | NODE_COORD_SECTION 12 | 1 82 76 13 | 2 96 44 14 | 3 50 5 15 | 4 49 8 16 | 5 13 7 17 | DEMAND_SECTION 18 | 1 0 19 | 2 19 20 | 3 21 21 | 4 6 22 | 5 19 23 | DEPOT_SECTION 24 | 1 25 | -1 26 | EOF 27 | "# 28 | .to_string(); 29 | let logger = Environment::default().logger; 30 | 31 | solve_vrp("tsplib", problem, "rosomaxa", 8, 200, logger); 32 | } 33 | -------------------------------------------------------------------------------- /experiments/heuristic-research/www/bootstrap.js: -------------------------------------------------------------------------------- 1 | init(); 2 | 3 | async function init() { 4 | const [{Chart, default: init, run_function_experiment, run_vrp_experiment, load_state, clear}, {main, setup}] = await Promise.all([ 5 | import("../pkg/heuristic_research.js"), 6 | import("./index.js"), 7 | ]); 8 | await init(); 9 | setup(Chart, run_function_experiment, run_vrp_experiment, load_state, clear); 10 | main(); 11 | } 12 | -------------------------------------------------------------------------------- /rosomaxa/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rosomaxa" 3 | version = "0.9.0" 4 | description = "A rosomaxa algorithm and other building blocks for creating a solver for optimization problems" 5 | authors.workspace = true 6 | license.workspace = true 7 | keywords.workspace = true 8 | categories.workspace = true 9 | readme.workspace = true 10 | homepage.workspace = true 11 | repository.workspace = true 12 | edition.workspace = true 13 | 14 | 15 | [dependencies] 16 | rand.workspace = true 17 | rayon.workspace = true 18 | rustc-hash.workspace = true 19 | rand_distr = "0.4.3" 20 | 21 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 22 | num_cpus = "1.17.0" 23 | 24 | [target.'cfg(target_arch = "wasm32")'.dependencies] 25 | getrandom = { version = "0.2.16", features = ["js"] } 26 | js-sys = "0.3.77" 27 | -------------------------------------------------------------------------------- /rosomaxa/README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | This crate exposes generalized hyper heuristics and some helper functionality which can be used to build a solver for 4 | various optimization problems. 5 | 6 | More details are coming. -------------------------------------------------------------------------------- /rosomaxa/src/algorithms/gsom/mod.rs: -------------------------------------------------------------------------------- 1 | //! Provides customized implementation of Growing Self Organizing Map. 2 | 3 | use crate::utils::Float; 4 | use std::fmt::Display; 5 | use std::ops::RangeBounds; 6 | 7 | mod contraction; 8 | pub(crate) use self::contraction::*; 9 | 10 | mod network; 11 | pub use self::network::*; 12 | 13 | mod node; 14 | pub use self::node::*; 15 | 16 | mod state; 17 | pub use self::state::*; 18 | 19 | /// Represents an input for network. 20 | pub trait Input: Send + Sync { 21 | /// Returns weights. 22 | fn weights(&self) -> &[Float]; 23 | } 24 | 25 | /// Represents input data storage. 26 | pub trait Storage: Display + Send + Sync { 27 | /// An input type. 28 | type Item: Input; 29 | 30 | /// Adds an input to the storage. 31 | fn add(&mut self, input: Self::Item); 32 | 33 | /// Returns iterator over all data. 34 | fn iter(&self) -> Box + '_>; 35 | 36 | /// Removes and returns all data from the storage. 37 | fn drain(&mut self, range: R) -> Vec 38 | where 39 | R: RangeBounds; 40 | 41 | /// Shrinks the storage to the specified size. 42 | fn resize(&mut self, size: usize); 43 | 44 | /// Returns size of the storage. 45 | fn size(&self) -> usize; 46 | } 47 | 48 | /// Represents a storage factory. 49 | pub trait StorageFactory: Send + Sync 50 | where 51 | C: Send + Sync, 52 | I: Input, 53 | S: Storage, 54 | { 55 | /// Returns a new storage. 56 | fn eval(&self, context: &C) -> S; 57 | } 58 | -------------------------------------------------------------------------------- /rosomaxa/src/algorithms/math/distance.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | #[path = "../../../tests/unit/algorithms/math/distance_test.rs"] 3 | mod distance_test; 4 | 5 | use crate::prelude::Float; 6 | use std::borrow::Borrow; 7 | 8 | /// Calculates relative distance between two vectors. As weights are not normalized, apply 9 | /// standardization using relative change: D = |x - y| / max(|x|, |y|) 10 | pub fn relative_distance(a: IA, b: IB) -> Float 11 | where 12 | IA: Iterator, 13 | IB: Iterator, 14 | IA::Item: Borrow, 15 | IB::Item: Borrow, 16 | { 17 | a.zip(b) 18 | .fold(Float::default(), |acc, (a, b)| { 19 | let (a, b) = (a.borrow(), b.borrow()); 20 | let divider = a.abs().max(b.abs()); 21 | let change = if divider == 0. { 0. } else { (a - b).abs() / divider }; 22 | 23 | acc + change * change 24 | }) 25 | .sqrt() 26 | } 27 | -------------------------------------------------------------------------------- /rosomaxa/src/algorithms/math/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains some statistic related functionality. 2 | 3 | mod distance; 4 | pub use self::distance::*; 5 | 6 | mod remedian; 7 | pub use self::remedian::{Remedian, RemedianUsize}; 8 | 9 | mod statistics; 10 | pub use self::statistics::*; 11 | -------------------------------------------------------------------------------- /rosomaxa/src/algorithms/mod.rs: -------------------------------------------------------------------------------- 1 | //! A collection of reusable algorithms without dependencies on any other module in the project. 2 | 3 | pub mod gsom; 4 | pub mod math; 5 | pub mod rl; 6 | -------------------------------------------------------------------------------- /rosomaxa/src/algorithms/rl/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains implementation of some reinforcement learning algorithms. 2 | 3 | mod slot_machine; 4 | pub use self::slot_machine::{SlotAction, SlotFeedback, SlotMachine}; 5 | -------------------------------------------------------------------------------- /rosomaxa/src/evolution/mod.rs: -------------------------------------------------------------------------------- 1 | //! Contains functionality to run evolution simulation. 2 | 3 | use crate::prelude::*; 4 | 5 | mod config; 6 | pub use self::config::*; 7 | 8 | mod simulator; 9 | pub use self::simulator::*; 10 | 11 | mod telemetry; 12 | pub use self::telemetry::*; 13 | 14 | pub mod objectives; 15 | pub mod strategies; 16 | 17 | /// Defines evolution result type. 18 | pub type EvolutionResult = Result<(Vec, Option), GenericError>; 19 | 20 | /// Provides the way to preprocess context before using it. 21 | pub trait HeuristicContextProcessing { 22 | /// A heuristic context type. 23 | type Context: HeuristicContext; 24 | /// A heuristic objective type. 25 | type Objective: HeuristicObjective; 26 | /// A solution type. 27 | type Solution: HeuristicSolution; 28 | 29 | /// Preprocess a context in order to replace usages of a given context with a new one. 30 | fn pre_process(&self, context: Self::Context) -> Self::Context; 31 | } 32 | 33 | /// Provides the way to modify solution before returning it. 34 | pub trait HeuristicSolutionProcessing { 35 | /// A solution type. 36 | type Solution: HeuristicSolution; 37 | 38 | /// Post processes solution. 39 | fn post_process(&self, solution: Self::Solution) -> Self::Solution; 40 | } 41 | -------------------------------------------------------------------------------- /rosomaxa/src/evolution/objectives.rs: -------------------------------------------------------------------------------- 1 | //! Specifies objective functions. 2 | 3 | use std::cmp::Ordering; 4 | 5 | /// A *heuristic objective* function defines a *total ordering relation* between any two solutions 6 | /// as a goal of optimization. 7 | pub trait HeuristicObjective: Send + Sync { 8 | /// The solution value type that we define the objective on. 9 | type Solution; 10 | 11 | /// An objective defines a total ordering between any two solution values. 12 | /// 13 | /// This answers the question, is solution `a` better, equal or worse than solution `b` according 14 | /// to the goal of optimization. 15 | fn total_order(&self, a: &Self::Solution, b: &Self::Solution) -> Ordering; 16 | } 17 | 18 | /// Calculates dominance order of two solutions using ordering functions. 19 | pub fn dominance_order<'a, T: 'a, Order, Iter>(a: &'a T, b: &'a T, ordering_fns: Iter) -> Ordering 20 | where 21 | Order: Fn(&'a T, &'a T) -> Ordering, 22 | Iter: Iterator, 23 | { 24 | let mut less_cnt = 0; 25 | let mut greater_cnt = 0; 26 | 27 | for ordering_fn in ordering_fns { 28 | match ordering_fn(a, b) { 29 | Ordering::Less => { 30 | less_cnt += 1; 31 | } 32 | Ordering::Greater => { 33 | greater_cnt += 1; 34 | } 35 | Ordering::Equal => {} 36 | } 37 | } 38 | 39 | if less_cnt > 0 && greater_cnt == 0 { 40 | Ordering::Less 41 | } else if greater_cnt > 0 && less_cnt == 0 { 42 | Ordering::Greater 43 | } else { 44 | debug_assert!((less_cnt > 0 && greater_cnt > 0) || (less_cnt == 0 && greater_cnt == 0)); 45 | Ordering::Equal 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /rosomaxa/src/evolution/strategies/mod.rs: -------------------------------------------------------------------------------- 1 | //! Specifies evolution strategies. 2 | 3 | use super::*; 4 | 5 | mod iterative; 6 | pub use self::iterative::Iterative; 7 | 8 | /// An evolution algorithm strategy. 9 | pub trait EvolutionStrategy { 10 | /// A heuristic context type. 11 | type Context: HeuristicContext; 12 | /// A heuristic objective type. 13 | type Objective: HeuristicObjective; 14 | /// A solution type. 15 | type Solution: HeuristicSolution; 16 | 17 | /// Runs evolution and returns a population with solution(-s). 18 | fn run( 19 | &mut self, 20 | heuristic_ctx: Self::Context, 21 | termination: Box>, 22 | ) -> EvolutionResult; 23 | } 24 | -------------------------------------------------------------------------------- /rosomaxa/src/prelude.rs: -------------------------------------------------------------------------------- 1 | //! This module reimports a commonly used types. 2 | 3 | pub use crate::HeuristicContext; 4 | pub use crate::HeuristicResult; 5 | pub use crate::HeuristicSolution; 6 | pub use crate::HeuristicSpeed; 7 | pub use crate::HeuristicStatistics; 8 | pub use crate::Stateful; 9 | 10 | pub use crate::evolution::EvolutionConfig; 11 | pub use crate::evolution::EvolutionConfigBuilder; 12 | pub use crate::evolution::HeuristicContextProcessing; 13 | pub use crate::evolution::HeuristicSolutionProcessing; 14 | pub use crate::evolution::InitialOperators; 15 | pub use crate::evolution::TelemetryMode; 16 | pub use crate::evolution::objectives::HeuristicObjective; 17 | pub use crate::evolution::strategies::EvolutionStrategy; 18 | 19 | pub use crate::population::HeuristicPopulation; 20 | pub use crate::population::SelectionPhase; 21 | pub use crate::population::{Rosomaxa, RosomaxaConfig}; 22 | 23 | pub use crate::hyper::HeuristicSearchOperator; 24 | pub use crate::hyper::HyperHeuristic; 25 | 26 | pub use crate::termination::Termination; 27 | 28 | pub use crate::utils::DefaultRandom; 29 | pub use crate::utils::Environment; 30 | pub use crate::utils::Float; 31 | pub use crate::utils::InfoLogger; 32 | pub use crate::utils::Noise; 33 | pub use crate::utils::Quota; 34 | pub use crate::utils::UnwrapValue; 35 | pub use crate::utils::short_type_name; 36 | pub use crate::utils::{GenericError, GenericResult}; 37 | pub use crate::utils::{Random, RandomGen}; 38 | -------------------------------------------------------------------------------- /rosomaxa/src/termination/max_generation.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | #[path = "../../tests/unit/termination/max_generation_test.rs"] 3 | mod max_generation_test; 4 | 5 | use super::*; 6 | use std::marker::PhantomData; 7 | 8 | /// A termination criteria which is in terminated state when maximum amount of generations is exceeded. 9 | pub struct MaxGeneration 10 | where 11 | C: HeuristicContext, 12 | O: HeuristicObjective, 13 | S: HeuristicSolution, 14 | { 15 | limit: usize, 16 | _marker: (PhantomData, PhantomData, PhantomData), 17 | } 18 | 19 | impl MaxGeneration 20 | where 21 | C: HeuristicContext, 22 | O: HeuristicObjective, 23 | S: HeuristicSolution, 24 | { 25 | /// Creates a new instance of `MaxGeneration`. 26 | pub fn new(limit: usize) -> Self { 27 | Self { limit, _marker: (Default::default(), Default::default(), Default::default()) } 28 | } 29 | } 30 | 31 | impl Termination for MaxGeneration 32 | where 33 | C: HeuristicContext, 34 | O: HeuristicObjective, 35 | S: HeuristicSolution, 36 | { 37 | type Context = C; 38 | type Objective = O; 39 | 40 | fn is_termination(&self, heuristic_ctx: &mut Self::Context) -> bool { 41 | heuristic_ctx.statistics().generation >= self.limit 42 | } 43 | 44 | fn estimate(&self, heuristic_ctx: &Self::Context) -> Float { 45 | (heuristic_ctx.statistics().generation as Float / self.limit as Float).min(1.) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /rosomaxa/src/termination/max_time.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::utils::Timer; 3 | use std::marker::PhantomData; 4 | 5 | /// A termination criteria which is in terminated state when max time elapsed. 6 | pub struct MaxTime 7 | where 8 | C: HeuristicContext, 9 | O: HeuristicObjective, 10 | S: HeuristicSolution, 11 | { 12 | start: Timer, 13 | limit_in_secs: Float, 14 | _marker: (PhantomData, PhantomData, PhantomData), 15 | } 16 | 17 | impl MaxTime 18 | where 19 | C: HeuristicContext, 20 | O: HeuristicObjective, 21 | S: HeuristicSolution, 22 | { 23 | /// Creates a new instance of `MaxTime`. 24 | pub fn new(limit_in_secs: Float) -> Self { 25 | Self { 26 | start: Timer::start(), 27 | limit_in_secs, 28 | _marker: (Default::default(), Default::default(), Default::default()), 29 | } 30 | } 31 | } 32 | 33 | impl Termination for MaxTime 34 | where 35 | C: HeuristicContext, 36 | O: HeuristicObjective, 37 | S: HeuristicSolution, 38 | { 39 | type Context = C; 40 | type Objective = O; 41 | 42 | fn is_termination(&self, _: &mut Self::Context) -> bool { 43 | self.start.elapsed_secs_as_float() > self.limit_in_secs 44 | } 45 | 46 | fn estimate(&self, _: &Self::Context) -> Float { 47 | (self.start.elapsed_secs_as_float() / self.limit_in_secs).min(1.) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /rosomaxa/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains helper functionality. 2 | 3 | mod environment; 4 | pub use self::environment::*; 5 | 6 | mod error; 7 | pub use self::error::*; 8 | 9 | mod iterators; 10 | pub use self::iterators::*; 11 | 12 | mod noise; 13 | pub use self::noise::*; 14 | 15 | mod parallel; 16 | pub use self::parallel::*; 17 | 18 | mod random; 19 | pub use self::random::*; 20 | 21 | mod timing; 22 | pub use self::timing::*; 23 | 24 | mod types; 25 | pub use self::types::*; 26 | -------------------------------------------------------------------------------- /rosomaxa/tests/helpers/algorithms/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod gsom; 2 | -------------------------------------------------------------------------------- /rosomaxa/tests/helpers/example.rs: -------------------------------------------------------------------------------- 1 | use crate::example::*; 2 | use crate::utils::{Environment, Float}; 3 | use crate::{TelemetryMode, get_default_population, get_default_selection_size}; 4 | use std::sync::Arc; 5 | 6 | /// Creates an example objective. 7 | pub fn create_example_objective() -> Arc { 8 | let fitness_fn = create_rosenbrock_function(); 9 | let weight_fn = Arc::new(|data: &[Float]| data.to_vec()); 10 | 11 | Arc::new(VectorObjective::new(fitness_fn, weight_fn)) 12 | } 13 | 14 | /// A helper method to create an example of VectorContext. 15 | pub fn create_default_heuristic_context() -> VectorContext { 16 | create_heuristic_context_with_solutions(vec![]) 17 | } 18 | 19 | /// A helper method to create an example of VectorContext with given solutions and objective function. 20 | pub fn create_heuristic_context_with_solutions(solutions: Vec>) -> VectorContext { 21 | let environment = Arc::new(Environment::default()); 22 | let objective = create_example_objective(); 23 | let selection_size = get_default_selection_size(environment.as_ref()); 24 | 25 | let mut population = 26 | get_default_population(objective.clone(), VectorRosomaxaContext, environment.clone(), selection_size); 27 | 28 | let solutions = solutions 29 | .into_iter() 30 | .map(|data| { 31 | let fitness = (objective.fitness_fn)(data.as_slice()); 32 | let weights = (objective.weight_fn)(data.as_slice()); 33 | VectorSolution::new(data, fitness, weights) 34 | }) 35 | .collect(); 36 | population.add_all(solutions); 37 | 38 | VectorContext::new(objective, population, TelemetryMode::None, environment) 39 | } 40 | -------------------------------------------------------------------------------- /rosomaxa/tests/helpers/macros.rs: -------------------------------------------------------------------------------- 1 | // See https://stackoverflow.com/questions/34662713/how-can-i-create-parameterized-tests-in-rust 2 | macro_rules! with_dollar_sign { 3 | ($($body:tt)*) => { 4 | macro_rules! __with_dollar_sign { $($body)* } 5 | __with_dollar_sign!($); 6 | } 7 | } 8 | 9 | /// A macro to a create parameterized test. 10 | #[macro_export] 11 | macro_rules! parameterized_test { 12 | ($name:ident, $args:pat, $body:tt) => { 13 | with_dollar_sign! { 14 | ($d:tt) => { 15 | macro_rules! $name { 16 | ($d($d pname:ident: $d values:expr_2021,)*) => { 17 | mod $name { 18 | use super::*; 19 | $d( 20 | #[test] 21 | fn $d pname() { 22 | let $args = $d values; 23 | $body 24 | } 25 | )* 26 | }}}}} 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /rosomaxa/tests/helpers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod algorithms; 2 | pub mod example; 3 | pub mod utils; 4 | 5 | #[cfg(test)] 6 | #[macro_use] 7 | pub mod macros; 8 | -------------------------------------------------------------------------------- /rosomaxa/tests/helpers/utils/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::{DefaultRandom, Random}; 2 | use std::sync::Arc; 3 | 4 | pub fn create_test_random() -> Arc { 5 | Arc::new(DefaultRandom::default()) 6 | } 7 | -------------------------------------------------------------------------------- /rosomaxa/tests/unit/algorithms/gsom/node_test.rs: -------------------------------------------------------------------------------- 1 | use crate::algorithms::gsom::{Coordinate, Node}; 2 | use crate::helpers::algorithms::gsom::{Data, DataStorage}; 3 | 4 | fn create_test_node(hit_memory_size: usize) -> Node { 5 | Node::new(Coordinate(0, 0), &[1., 2.], 0., hit_memory_size, DataStorage::default()) 6 | } 7 | 8 | #[test] 9 | fn can_track_last_hits() { 10 | let hit_memory_size = 100; 11 | let mut node = create_test_node(hit_memory_size); 12 | 13 | node.new_hit(1); 14 | assert_eq!(node.get_last_hits(1), 1); 15 | assert_eq!(node.get_last_hits(2), 1); 16 | 17 | node.new_hit(3); 18 | assert_eq!(node.get_last_hits(3), 2); 19 | 20 | node.new_hit(hit_memory_size); 21 | assert_eq!(node.get_last_hits(hit_memory_size), 3); 22 | 23 | node.new_hit(hit_memory_size + 1); 24 | assert_eq!(node.get_last_hits(hit_memory_size + 1), 3); 25 | 26 | node.new_hit(hit_memory_size + 100); 27 | assert_eq!(node.get_last_hits(hit_memory_size + 100), 2); 28 | } 29 | -------------------------------------------------------------------------------- /rosomaxa/tests/unit/algorithms/gsom/state_test.rs: -------------------------------------------------------------------------------- 1 | use crate::algorithms::gsom::get_network_state; 2 | use crate::helpers::algorithms::gsom::create_test_network; 3 | 4 | #[test] 5 | fn can_get_state() { 6 | let network = create_test_network(false); 7 | 8 | let state = get_network_state(&network); 9 | 10 | assert_eq!(state.nodes.len(), 4); 11 | assert_eq!(state.shape, (0..1, 0..1, 3)); 12 | } 13 | -------------------------------------------------------------------------------- /rosomaxa/tests/unit/termination/max_generation_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::Timer; 3 | use crate::helpers::example::create_default_heuristic_context; 4 | 5 | parameterized_test! {can_detect_termination, (generation, limit, expected), { 6 | can_detect_termination_impl(generation, limit, expected); 7 | }} 8 | 9 | can_detect_termination! { 10 | case_01: (11, 10, true), 11 | case_02: (9, 10, false), 12 | case_03: (10, 10, true), 13 | } 14 | 15 | fn can_detect_termination_impl(generation: usize, limit: usize, expected: bool) { 16 | let mut context = create_default_heuristic_context(); 17 | 18 | (0..=generation).for_each(|_| { 19 | context.on_generation(vec![], 0.1, Timer::start()); 20 | }); 21 | 22 | let result = MaxGeneration::<_, _, _>::new(limit).is_termination(&mut context); 23 | 24 | assert_eq!(result, expected); 25 | } 26 | -------------------------------------------------------------------------------- /rosomaxa/tests/unit/termination/target_proximity_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::helpers::example::create_heuristic_context_with_solutions; 3 | 4 | parameterized_test! {can_use_target_proximity, (solutions, target_fitness, distance_threshold, expected), { 5 | can_use_target_proximity_impl(solutions, target_fitness, distance_threshold, expected); 6 | }} 7 | 8 | can_use_target_proximity! { 9 | case01: (vec![vec![0.5, 0.5]], vec![0.], 0.1, false), 10 | case02: (vec![vec![0., 0.]], vec![0.89], 0.1, false), 11 | case03: (vec![vec![0., 0.]], vec![0.91], 0.1, true), 12 | } 13 | 14 | fn can_use_target_proximity_impl( 15 | solutions: Vec>, 16 | target_fitness: Vec, 17 | distance_threshold: Float, 18 | expected: bool, 19 | ) { 20 | let mut context = create_heuristic_context_with_solutions(solutions); 21 | 22 | let result = TargetProximity::<_, _, _>::new(target_fitness, distance_threshold).is_termination(&mut context); 23 | 24 | assert_eq!(result, expected) 25 | } 26 | -------------------------------------------------------------------------------- /rosomaxa/tests/unit/utils/parallel_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use std::collections::HashMap; 3 | 4 | #[test] 5 | fn can_use_map_reduce_for_vec() { 6 | let vec = vec![1, 2, 3]; 7 | 8 | let result = map_reduce(&vec, |item| *item, || 0, |a, b| a + b); 9 | 10 | assert_eq!(result, 6); 11 | } 12 | 13 | #[test] 14 | fn can_use_map_reduce_for_map() { 15 | let mut map = HashMap::new(); 16 | map.insert(1, "1"); 17 | map.insert(2, "2"); 18 | 19 | let result = map_reduce(&map, |(key, _)| *key, || 0, |a, b| a + b); 20 | 21 | assert_eq!(result, 3); 22 | } 23 | 24 | #[test] 25 | fn can_use_map_reduce_for_slice() { 26 | let vec = vec![1, 2, 3]; 27 | 28 | let result = map_reduce(vec.as_slice(), |item| *item, || 0, |a, b| a + b); 29 | 30 | assert_eq!(result, 6); 31 | } 32 | -------------------------------------------------------------------------------- /rosomaxa/tests/unit/utils/random_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[test] 4 | fn can_return_weights() { 5 | let random = DefaultRandom::default(); 6 | let weights = &[100, 50, 20]; 7 | let experiments = 10000_usize; 8 | let total_sum = weights.iter().sum::(); 9 | let mut counter = [0_usize; 3]; 10 | 11 | (0..experiments).for_each(|_| { 12 | let idx = random.weighted(weights); 13 | *counter.get_mut(idx).unwrap() += 1; 14 | }); 15 | 16 | weights.iter().enumerate().for_each(|(idx, weight)| { 17 | let actual_ratio = counter[idx] as Float / experiments as Float; 18 | let expected_ratio = *weight as Float / total_sum as Float; 19 | 20 | assert!((actual_ratio - expected_ratio).abs() < 0.05); 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /solve_problem.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # A handy script to run solver on pragmatic problem using routing approximation. 3 | # Output is a solution in pragmatic and geojson formats stored in the same folder 4 | # using the problem's file pattern. 5 | 6 | #set -eux 7 | 8 | if [[ $# -eq 0 ]] ; then 9 | echo -e '\033[0;31mExpected a path to file with pragmatic problem definition\033[0m' 10 | echo "Hint: try 'examples/data/pragmatic/objectives/berlin.default.problem.json'" 11 | exit 0 12 | fi 13 | 14 | ALL_ARGS=("$@") 15 | PROBLEM_FILE_PATH="$1" 16 | PROBLEM_FILE_BASE="${PROBLEM_FILE_PATH%.*}" 17 | REST_ARGS=("${ALL_ARGS[@]:1}") 18 | 19 | cargo run -p vrp-cli --release -- solve pragmatic "${PROBLEM_FILE_PATH}" \ 20 | --out-result "${PROBLEM_FILE_BASE}_solution.json" \ 21 | --geo-json "${PROBLEM_FILE_BASE}_solution.geojson" \ 22 | --log \ 23 | --check \ 24 | "${REST_ARGS[@]}" -------------------------------------------------------------------------------- /vrp-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vrp-cli" 3 | description = "A command line interface for VRP solver" 4 | version.workspace = true 5 | authors.workspace = true 6 | license.workspace = true 7 | keywords.workspace = true 8 | categories.workspace = true 9 | readme.workspace = true 10 | homepage.workspace = true 11 | repository.workspace = true 12 | edition.workspace = true 13 | 14 | [features] 15 | default = ["vrp-core", "csv-format", "scientific-format"] 16 | 17 | csv-format = ["csv"] 18 | scientific-format = ["vrp-scientific"] 19 | py_bindings = ["dep:pyo3"] 20 | 21 | [lib] 22 | name = "vrp_cli" 23 | crate-type = ["cdylib", "lib"] 24 | 25 | [dependencies] 26 | vrp-core = { workspace = true, optional = true } 27 | vrp-scientific = { workspace = true, optional = true} 28 | vrp-pragmatic.workspace = true 29 | 30 | serde.workspace = true 31 | serde_json.workspace = true 32 | 33 | csv = { version = "1.3.1", optional = true } 34 | 35 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 36 | clap = "4.5.39" 37 | ctrlc = { version = "3.4.7", features = ["termination"] } 38 | num_cpus = "1.17.0" 39 | 40 | # see https://github.com/xd009642/tarpaulin/issues/1092 41 | [target.'cfg(all(not(target_arch = "wasm32"), not(tarpaulin)))'.dependencies] 42 | pyo3 = { version= "0.25.0", features=["extension-module"], optional = true } 43 | 44 | [target.'cfg(target_arch = "wasm32")'.dependencies] 45 | wasm-bindgen = { version = "0.2.100" } 46 | serde-wasm-bindgen = "0.6.5" 47 | js-sys = "0.3.77" 48 | 49 | [dev-dependencies] 50 | tempfile = "3.20.0" 51 | -------------------------------------------------------------------------------- /vrp-cli/README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | The `cli` crate implements command line interface to **Vehicle Routing Problem** solver functionality. 4 | 5 | 6 | Please check [the repository](https://github.com/reinterpretcat/vrp) for more details. -------------------------------------------------------------------------------- /vrp-cli/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["maturin>=0.14,<0.15"] 3 | build-backend = "maturin" 4 | 5 | [tool.maturin] 6 | bindings = "pyo3" 7 | features = ["py_bindings"] 8 | strip = true -------------------------------------------------------------------------------- /vrp-cli/src/commands/check.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | #[path = "../../tests/unit/commands/check_test.rs"] 3 | mod check_test; 4 | 5 | use super::*; 6 | use vrp_core::prelude::GenericError; 7 | 8 | const FORMAT_ARG_NAME: &str = "FORMAT"; 9 | const PROBLEM_ARG_NAME: &str = "problem-file"; 10 | const SOLUTION_ARG_NAME: &str = "solution-file"; 11 | const MATRIX_ARG_NAME: &str = "matrix"; 12 | 13 | pub fn get_check_app() -> Command { 14 | Command::new("check") 15 | .about("Provides the way to check solution feasibility") 16 | .arg(Arg::new(FORMAT_ARG_NAME).help("Specifies input type").required(true).value_parser(["pragmatic"]).index(1)) 17 | .arg( 18 | Arg::new(PROBLEM_ARG_NAME) 19 | .help("Sets input files which contain a VRP definition") 20 | .short('p') 21 | .long(PROBLEM_ARG_NAME) 22 | .required(true) 23 | .num_args(1..), 24 | ) 25 | .arg(Arg::new(SOLUTION_ARG_NAME).help("Sets solution file").short('s').long(SOLUTION_ARG_NAME).required(true)) 26 | .arg( 27 | Arg::new(MATRIX_ARG_NAME) 28 | .help("Specifies path to file with routing matrix") 29 | .short('m') 30 | .long(MATRIX_ARG_NAME) 31 | .required(false) 32 | .num_args(1..), 33 | ) 34 | } 35 | 36 | pub fn run_check(matches: &ArgMatches) -> Result<(), GenericError> { 37 | let input_format = matches.get_one::(FORMAT_ARG_NAME).unwrap(); 38 | check_solution(matches, input_format, PROBLEM_ARG_NAME, SOLUTION_ARG_NAME, MATRIX_ARG_NAME) 39 | } 40 | -------------------------------------------------------------------------------- /vrp-cli/src/extensions/analyze/mod.rs: -------------------------------------------------------------------------------- 1 | //! Provides functionality for problem/solution analysis. 2 | 3 | mod clusters; 4 | pub use self::clusters::*; 5 | -------------------------------------------------------------------------------- /vrp-cli/src/extensions/generate/prototype.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | #[path = "../../../tests/unit/extensions/generate/prototype_test.rs"] 3 | mod prototype_test; 4 | 5 | use super::*; 6 | use vrp_core::prelude::Float; 7 | use vrp_pragmatic::format::Location; 8 | use vrp_pragmatic::format::problem::Problem; 9 | 10 | /// Generates meaningful problem from the prototype. 11 | /// There is another problem generation implementation in `vrp-pragmatic` crate, used by tests. 12 | /// Its main goal is to discover problem space by generating many, potentially unrealistic, problems 13 | /// using property based approach. This implementation, in contrast, focuses on generating realistic 14 | /// problems. 15 | pub(crate) fn generate_from_prototype( 16 | problem: &Problem, 17 | locations: Option>, 18 | jobs_size: usize, 19 | vehicle_types_size: usize, 20 | area_size: Option, 21 | ) -> Result { 22 | if problem.plan.jobs.len() < 3 { 23 | return Err("at least three jobs should be defined".into()); 24 | } 25 | 26 | Ok(Problem { 27 | plan: generate_plan(problem, locations, jobs_size, area_size)?, 28 | fleet: generate_fleet(problem, vehicle_types_size), 29 | objectives: problem.objectives.clone(), 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /vrp-cli/src/extensions/import/mod.rs: -------------------------------------------------------------------------------- 1 | //! Import command helpers 2 | 3 | mod csv; 4 | pub use self::csv::*; 5 | 6 | use std::io::{BufReader, Read}; 7 | use vrp_core::prelude::GenericError; 8 | use vrp_pragmatic::format::problem::Problem; 9 | 10 | /// Imports solution from specific format into pragmatic. 11 | pub fn import_problem( 12 | input_format: &str, 13 | readers: Option>>, 14 | ) -> Result { 15 | match (input_format, readers) { 16 | ("csv", Some(mut readers)) if readers.len() == 2 => { 17 | let jobs = readers.swap_remove(0); 18 | let vehicles = readers.swap_remove(0); 19 | read_csv_problem(jobs, vehicles).map_err(|err| format!("cannot read csv: {err}").into()) 20 | } 21 | ("csv", _) => Err("csv format expects two files with jobs and vehicles as an input".into()), 22 | _ => Err(format!("unknown format: '{input_format}'").into()), 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /vrp-cli/src/extensions/mod.rs: -------------------------------------------------------------------------------- 1 | //! Module provides various helper functionality. 2 | 3 | #[cfg(not(target_arch = "wasm32"))] 4 | pub mod analyze; 5 | #[cfg(not(target_arch = "wasm32"))] 6 | pub mod check; 7 | #[cfg(not(target_arch = "wasm32"))] 8 | pub mod generate; 9 | 10 | pub mod import; 11 | pub mod solve; 12 | -------------------------------------------------------------------------------- /vrp-cli/src/extensions/solve/mod.rs: -------------------------------------------------------------------------------- 1 | //! Solve command helpers 2 | 3 | pub mod config; 4 | pub mod formats; 5 | -------------------------------------------------------------------------------- /vrp-cli/tests/features/mod.rs: -------------------------------------------------------------------------------- 1 | mod generate; 2 | mod solve; 3 | -------------------------------------------------------------------------------- /vrp-cli/tests/features/solve.rs: -------------------------------------------------------------------------------- 1 | use crate::extensions::solve::config::{create_builder_from_config, read_config}; 2 | use std::fs::File; 3 | use std::io::BufReader; 4 | use std::sync::Arc; 5 | use vrp_core::prelude::Solver; 6 | use vrp_pragmatic::format::problem::PragmaticProblem; 7 | 8 | #[test] 9 | fn can_solve_problem_using_full_config() { 10 | let problem = Arc::new( 11 | BufReader::new(File::open("../examples/data/pragmatic/simple.basic.problem.json").unwrap()) 12 | .read_pragmatic() 13 | .unwrap(), 14 | ); 15 | let reader = BufReader::new(File::open("../examples/data/config/config.full.json").unwrap()); 16 | // TODO override termination to avoid test timeout on CI 17 | let mut config = read_config(reader).unwrap(); 18 | if let Some(initial) = config.evolution.as_mut().and_then(|evolution| evolution.initial.as_mut()) { 19 | initial.alternatives.max_size = 1; 20 | } 21 | if let Some(termination) = config.termination.as_mut() { 22 | termination.max_generations = Some(1); 23 | termination.max_time = None; 24 | termination.variation = None; 25 | } 26 | 27 | let solution = create_builder_from_config(problem.clone(), Default::default(), &config) 28 | .unwrap() 29 | .build() 30 | .map(|config| Solver::new(problem.clone(), config)) 31 | .unwrap() 32 | .solve() 33 | .unwrap(); 34 | 35 | assert!(!solution.routes.is_empty()) 36 | } 37 | -------------------------------------------------------------------------------- /vrp-cli/tests/helpers/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | #[path = "../../../vrp-core/tests/helpers/macros.rs"] 3 | #[macro_use] 4 | pub mod macros; 5 | 6 | pub mod generate; 7 | -------------------------------------------------------------------------------- /vrp-cli/tests/unit/commands/analyze_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::cli::{get_app, run_subcommand}; 3 | 4 | const PRAGMATIC_PROBLEM_PATH: &str = "../examples/data/pragmatic/simple.basic.problem.json"; 5 | 6 | #[test] 7 | fn can_run_analyze_dbscan() { 8 | let tmpfile = tempfile::NamedTempFile::new().unwrap(); 9 | let args = vec![ 10 | "vrp-cli", 11 | "analyze", 12 | "dbscan", 13 | "pragmatic", 14 | PRAGMATIC_PROBLEM_PATH, 15 | "--out-result", 16 | tmpfile.path().to_str().unwrap(), 17 | ]; 18 | let matches = get_app().try_get_matches_from(args).unwrap(); 19 | 20 | run_subcommand(matches); 21 | } 22 | 23 | #[test] 24 | fn can_detect_wrong_argument_in_dbscan() { 25 | let args = vec!["analyze", "dbscan", "solomon", PRAGMATIC_PROBLEM_PATH, "--out-result", "/some/path"]; 26 | 27 | assert!(get_analyze_app().try_get_matches_from(args).is_err()); 28 | } 29 | 30 | #[test] 31 | fn can_run_analyze_kmedoids() { 32 | let tmpfile = tempfile::NamedTempFile::new().unwrap(); 33 | let args = vec![ 34 | "vrp-cli", 35 | "analyze", 36 | "kmedoids", 37 | "pragmatic", 38 | PRAGMATIC_PROBLEM_PATH, 39 | "-k", 40 | "3", 41 | "--out-result", 42 | tmpfile.path().to_str().unwrap(), 43 | ]; 44 | let matches = get_app().try_get_matches_from(args).unwrap(); 45 | 46 | run_subcommand(matches); 47 | } 48 | -------------------------------------------------------------------------------- /vrp-cli/tests/unit/commands/check_test.rs: -------------------------------------------------------------------------------- 1 | use crate::cli::{get_app, run_subcommand}; 2 | 3 | const PRAGMATIC_PROBLEM_PATH: &str = "../examples/data/pragmatic/simple.basic.problem.json"; 4 | const PRAGMATIC_MATRIX_PATH: &str = "../examples/data/pragmatic/simple.basic.matrix.json"; 5 | const PRAGMATIC_SOLUTION_PATH: &str = "../examples/data/pragmatic/simple.basic.solution.json"; 6 | 7 | #[test] 8 | fn can_run_check_solution() { 9 | let args = vec![ 10 | "vrp-cli", 11 | "check", 12 | "pragmatic", 13 | "--problem-file", 14 | PRAGMATIC_PROBLEM_PATH, 15 | "--matrix", 16 | PRAGMATIC_MATRIX_PATH, 17 | "--solution-file", 18 | PRAGMATIC_SOLUTION_PATH, 19 | ]; 20 | let matches = get_app().try_get_matches_from(args).unwrap(); 21 | 22 | run_subcommand(matches); 23 | } 24 | -------------------------------------------------------------------------------- /vrp-cli/tests/unit/commands/generate_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::cli::{get_app, run_subcommand}; 3 | use vrp_cli::pragmatic::format::problem::PragmaticProblem; 4 | 5 | const PRAGMATIC_PROBLEM_PATH: &str = "../examples/data/pragmatic/simple.basic.problem.json"; 6 | 7 | #[test] 8 | fn can_generate_problem_from_args() { 9 | let tmpfile = tempfile::NamedTempFile::new().unwrap(); 10 | let args = vec![ 11 | "vrp-cli", 12 | "generate", 13 | "pragmatic", 14 | "--prototypes", 15 | PRAGMATIC_PROBLEM_PATH, 16 | "--jobs-size", 17 | "100", 18 | "--vehicles-size", 19 | "10", 20 | "--out-result", 21 | tmpfile.path().to_str().unwrap(), 22 | ]; 23 | let matches = get_app().try_get_matches_from(args).unwrap(); 24 | 25 | run_subcommand(matches); 26 | 27 | let problem = BufReader::new(tmpfile.as_file()).read_pragmatic().unwrap(); 28 | assert_eq!(problem.jobs.size(), 100); 29 | assert_eq!(problem.fleet.vehicles.len(), 10); 30 | } 31 | -------------------------------------------------------------------------------- /vrp-cli/tests/unit/commands/import_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::cli::{get_app, run_subcommand}; 3 | use vrp_cli::pragmatic::format::problem::PragmaticProblem; 4 | 5 | const CSV_JOBS_PATH: &str = "../examples/data/csv/jobs.csv"; 6 | const VEHICLES_JOBS_PATH: &str = "../examples/data/csv/vehicles.csv"; 7 | 8 | #[test] 9 | fn can_import_csv_problem_from_args() { 10 | let tmpfile = tempfile::NamedTempFile::new().unwrap(); 11 | let args = vec![ 12 | "vrp-cli", 13 | "import", 14 | "csv", 15 | "--input-files", 16 | CSV_JOBS_PATH, 17 | VEHICLES_JOBS_PATH, 18 | "--out-result", 19 | tmpfile.path().to_str().unwrap(), 20 | ]; 21 | let matches = get_app().try_get_matches_from(args).unwrap(); 22 | 23 | run_subcommand(matches); 24 | 25 | let problem = BufReader::new(tmpfile.as_file()).read_pragmatic().unwrap(); 26 | assert_eq!(problem.jobs.size(), 3); 27 | assert_eq!(problem.fleet.vehicles.len(), 30); 28 | } 29 | -------------------------------------------------------------------------------- /vrp-cli/tests/unit/extensions/analyze/clusters_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use std::fs::File; 3 | use std::io::BufReader; 4 | use vrp_pragmatic::format::problem::{PragmaticProblem, deserialize_problem}; 5 | use vrp_pragmatic::format::solution::serialize_named_locations_as_geojson; 6 | 7 | #[test] 8 | pub fn can_get_dbscan_clusters() { 9 | can_get_clusters(|problem| get_dbscan_clusters(problem, None, None)); 10 | } 11 | 12 | #[test] 13 | pub fn can_get_kmedoids_clusters() { 14 | can_get_clusters(|problem| get_k_medoids_clusters(problem, 2)); 15 | } 16 | 17 | type LocationResult = GenericResult>; 18 | 19 | fn can_get_clusters(clusters_fn: fn(&Problem) -> LocationResult) { 20 | let problem_reader = BufReader::new( 21 | File::open("../examples/data/pragmatic/benches/simple.deliveries.100.json").expect("cannot read problem file"), 22 | ); 23 | 24 | let problem = deserialize_problem(problem_reader).unwrap().read_pragmatic().unwrap(); 25 | let locations = clusters_fn(&problem).unwrap(); 26 | let clusters = serialize_named_locations_as_geojson(&locations).unwrap(); 27 | 28 | assert!(clusters.contains("features")); 29 | assert!(clusters.contains("geometry")); 30 | assert!(clusters.contains("Point")); 31 | } 32 | -------------------------------------------------------------------------------- /vrp-cli/tests/unit/extensions/generate/fleet_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::helpers::generate::{create_empty_plan, create_test_vehicle_type}; 3 | use vrp_pragmatic::format::problem::MatrixProfile; 4 | 5 | #[test] 6 | fn can_generate_fleet_of_specific_size() { 7 | let prototype = Problem { 8 | plan: create_empty_plan(), 9 | fleet: Fleet { 10 | vehicles: vec![create_test_vehicle_type()], 11 | profiles: vec![MatrixProfile { name: "normal_car".to_string(), speed: None }], 12 | resources: None, 13 | }, 14 | objectives: None, 15 | }; 16 | 17 | let generated = generate_fleet(&prototype, 2); 18 | 19 | assert_eq!(generated.vehicles.len(), 2); 20 | assert_eq!(generated.profiles.len(), 1); 21 | } 22 | -------------------------------------------------------------------------------- /vrp-cli/tests/unit/extensions/generate/plan_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::helpers::generate::create_test_job; 3 | 4 | #[test] 5 | fn can_generate_bounding_box() { 6 | let plan = Plan { 7 | jobs: vec![create_test_job(-1., 1.), create_test_job(1., 0.), create_test_job(3., 1.), create_test_job(1., 2.)], 8 | relations: None, 9 | clustering: None, 10 | }; 11 | 12 | let ((min_lat, min_lng), (max_lat, max_lng)) = get_bounding_box_from_plan(&plan); 13 | 14 | assert_eq!(min_lat, -1.); 15 | assert_eq!(min_lng, 0.); 16 | assert_eq!(max_lat, 3.); 17 | assert_eq!(max_lng, 2.); 18 | } 19 | 20 | #[test] 21 | fn can_get_bounding_box_from_size() { 22 | let plan = Plan { 23 | jobs: vec![create_test_job(0., 1.), create_test_job(1., 0.), create_test_job(0., 0.), create_test_job(1., 1.)], 24 | relations: None, 25 | clustering: None, 26 | }; 27 | 28 | let ((min_lat, min_lng), (max_lat, max_lng)) = get_bounding_box_from_size(&plan, 100.); 29 | 30 | assert!((min_lat - 0.499101).abs() < 1E-6); 31 | assert!((min_lng - 0.499101).abs() < 1E-6); 32 | assert!((max_lat - 0.500898).abs() < 1E-6); 33 | assert!((max_lng - 0.500898).abs() < 1E-6); 34 | } 35 | -------------------------------------------------------------------------------- /vrp-cli/tests/unit/extensions/generate/prototype_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::helpers::generate::*; 3 | use vrp_pragmatic::format::problem::*; 4 | 5 | #[test] 6 | fn can_generate_jobs_with_time_windows() { 7 | let problem = Problem { 8 | plan: Plan { 9 | jobs: vec![ 10 | create_test_job(-1., 1.), 11 | create_test_job(1., 0.), 12 | create_test_job(3., 1.), 13 | create_test_job(1., 2.), 14 | ], 15 | ..create_empty_plan() 16 | }, 17 | fleet: Fleet { 18 | vehicles: vec![create_test_vehicle_type()], 19 | profiles: vec![create_test_vehicle_profile()], 20 | resources: None, 21 | }, 22 | objectives: None, 23 | }; 24 | 25 | let result = 26 | generate_from_prototype(&problem, None, 10, 2, None).unwrap_or_else(|err| panic!("cannot generate: '{err}'")); 27 | 28 | assert_eq!(result.plan.jobs.len(), 10); 29 | assert_eq!( 30 | result 31 | .plan 32 | .jobs 33 | .first() 34 | .expect("No job") 35 | .pickups 36 | .as_ref() 37 | .expect("No delivery") 38 | .first() 39 | .expect("No job task") 40 | .places 41 | .first() 42 | .expect("No job place") 43 | .times, 44 | Some(vec![create_test_time_window()]) 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /vrp-cli/tests/unit/main_test.rs: -------------------------------------------------------------------------------- 1 | use crate::cli::*; 2 | 3 | #[test] 4 | fn can_get_app_name() { 5 | let app = get_app(); 6 | 7 | assert_eq!(app.get_name(), "Vehicle Routing Problem Solver"); 8 | } 9 | -------------------------------------------------------------------------------- /vrp-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vrp-core" 3 | description = "A core algorithms to solve a Vehicle Routing Problem" 4 | version.workspace = true 5 | authors.workspace = true 6 | license.workspace = true 7 | keywords.workspace = true 8 | categories.workspace = true 9 | readme.workspace = true 10 | homepage.workspace = true 11 | repository.workspace = true 12 | edition.workspace = true 13 | 14 | [dependencies] 15 | rosomaxa.workspace = true 16 | 17 | rand.workspace = true 18 | rayon.workspace = true 19 | rustc-hash.workspace = true 20 | paste.workspace = true 21 | lazy_static.workspace = true 22 | 23 | nohash-hasher = "0.2.0" 24 | tinyvec = { version = "1.9.0", features = ["alloc"] } 25 | -------------------------------------------------------------------------------- /vrp-core/README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | The `core` crate contains main buildings blocks for constructing heuristics and metaheuristic to solve rich 4 | **Vehicle Routing Problem**. 5 | 6 | Please check [the repository](https://github.com/reinterpretcat/vrp) for more details. -------------------------------------------------------------------------------- /vrp-core/examples/README.md: -------------------------------------------------------------------------------- 1 | # VRP examples 2 | 3 | To run any example, from within the repo, run `cargo run --example ` where `` is the name of the 4 | file without the .rs extension. 5 | -------------------------------------------------------------------------------- /vrp-core/examples/common/routing.rs: -------------------------------------------------------------------------------- 1 | //! This code is used by multiple examples. 2 | 3 | use vrp_core::prelude::*; 4 | 5 | /// Gets a routing matrix for 5 unique locations. 6 | pub fn define_routing_data() -> GenericResult { 7 | // define distance/duration matrix (use the same data for both) 8 | // as we have five locations, we need to define 5x5 matrix, flatten to 1 dimension: 9 | #[rustfmt::skip] 10 | let routing_data = vec![ 11 | // 0 1 2 3 4 12 | 0., 500., 520., 530., 540., // 0 13 | 500., 0., 30., 40., 50., // 1 14 | 520., 30., 0., 20., 25., // 2 15 | 530., 40., 20., 0., 15., // 3 16 | 540., 50., 25., 15., 0. // 4 17 | ]; 18 | let (durations, distances) = (routing_data.clone(), routing_data); 19 | 20 | // `SimpleTransportCost` provides a simple way to use single routing matrix for any vehicle in the problem 21 | SimpleTransportCost::new(durations, distances) 22 | } 23 | -------------------------------------------------------------------------------- /vrp-core/src/algorithms/clustering/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains implementation of various clustering algorithms. 2 | 3 | pub mod dbscan; 4 | pub mod kmedoids; 5 | -------------------------------------------------------------------------------- /vrp-core/src/algorithms/geometry/mod.rs: -------------------------------------------------------------------------------- 1 | //! A module with geometry primitives. 2 | 3 | mod point; 4 | pub use self::point::Point; 5 | -------------------------------------------------------------------------------- /vrp-core/src/algorithms/mod.rs: -------------------------------------------------------------------------------- 1 | //! A collection of reusable algorithms without dependencies on any other module in the project. 2 | 3 | pub mod clustering; 4 | pub mod geometry; 5 | pub mod lkh; 6 | pub mod structures; 7 | -------------------------------------------------------------------------------- /vrp-core/src/algorithms/structures/mod.rs: -------------------------------------------------------------------------------- 1 | //! Contains some axillary data structures used in the algorithms. 2 | 3 | mod bitvec; 4 | pub use self::bitvec::BitVec; 5 | -------------------------------------------------------------------------------- /vrp-core/src/construction/clustering/dbscan/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module provides functionality which clusters jobs using DBSCAN algorithm. 2 | 3 | mod neighbour_clusters; 4 | pub use self::neighbour_clusters::create_job_clusters; 5 | 6 | use crate::algorithms::clustering::dbscan::create_clusters; 7 | use crate::algorithms::geometry::Point; 8 | use crate::models::common::Profile; 9 | use crate::models::problem::{Job, Single}; 10 | use crate::prelude::{Cost, Fleet}; 11 | use rosomaxa::prelude::*; 12 | use std::collections::HashSet; 13 | use std::sync::Arc; 14 | 15 | /// Gets max curvature approximation: for each point p on the curve, find the one with the maximum 16 | /// distance d to a line drawn from the first to the last point of the curves. 17 | fn get_max_curvature(values: &[Point]) -> Float { 18 | if values.is_empty() { 19 | return 0.; 20 | } 21 | 22 | let first = values.first().unwrap(); 23 | let last = values.last().unwrap(); 24 | 25 | values 26 | .iter() 27 | .fold((0., Float::MIN), |acc, p| { 28 | let distance = p.distance_to_line(first, last); 29 | 30 | if distance > acc.1 { (p.y, distance) } else { acc } 31 | }) 32 | .0 33 | } 34 | 35 | fn job_has_locations(job: &Job) -> bool { 36 | let has_location = |single: &Arc| single.places.iter().any(|place| place.location.is_some()); 37 | 38 | match &job { 39 | Job::Single(single) => has_location(single), 40 | Job::Multi(multi) => multi.jobs.iter().any(has_location), 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /vrp-core/src/construction/clustering/kmedoids/mod.rs: -------------------------------------------------------------------------------- 1 | //! Contains implementation of k-medoids clustering algorithm. 2 | 3 | mod multi_tier_clusters; 4 | pub use self::multi_tier_clusters::create_multi_tier_clusters; 5 | -------------------------------------------------------------------------------- /vrp-core/src/construction/clustering/kmedoids/multi_tier_clusters.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | #[path = "../../../../tests/unit/construction/clustering/kmedoids/multi_tier_clusters_test.rs"] 3 | mod multi_tier_clusters_test; 4 | 5 | use crate::algorithms::clustering::kmedoids::create_kmedoids; 6 | use crate::models::common::Profile; 7 | use crate::prelude::{GenericResult, Location, TransportCost}; 8 | use std::collections::HashMap; 9 | 10 | /// Represents a single tier of multiple location clusters. 11 | pub type LocationClusters = HashMap>; 12 | 13 | /// Creates multi-tier clusters of jobs using DBSCAN algorithm. 14 | /// Returns tiers of clusters starting from the finer grained (lowest epsilon). 15 | pub fn create_multi_tier_clusters( 16 | profile: Profile, 17 | transport: &(dyn TransportCost), 18 | ) -> GenericResult> { 19 | let size = transport.size(); 20 | let limit = size / 3; 21 | 22 | let points = (0..size).collect::>(); 23 | 24 | let location_clusters = [2, 3, 4, 5, 8, 10, 12, 16, 32, 64] 25 | .iter() 26 | .filter(|&&k| k <= limit) 27 | .map(|&k| create_kmedoids(&points, k, |from, to| transport.distance_approx(&profile, *from, *to))) 28 | .filter(|clusters| !clusters.is_empty()) 29 | .collect(); 30 | 31 | Ok(location_clusters) 32 | } 33 | -------------------------------------------------------------------------------- /vrp-core/src/construction/clustering/mod.rs: -------------------------------------------------------------------------------- 1 | //! Contains implementation of job clustering algorithms. 2 | 3 | pub mod dbscan; 4 | pub mod kmedoids; 5 | pub mod vicinity; 6 | -------------------------------------------------------------------------------- /vrp-core/src/construction/enablers/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains feature extension functionality which can be used to work with the same aspects 2 | //! from different features. 3 | 4 | mod conditional_job; 5 | pub use self::conditional_job::*; 6 | 7 | mod departure_time; 8 | pub use self::departure_time::*; 9 | 10 | mod feature_combinator; 11 | pub use self::feature_combinator::*; 12 | 13 | mod multi_trip; 14 | pub use self::multi_trip::*; 15 | 16 | mod only_vehicle_activity_cost; 17 | pub use self::only_vehicle_activity_cost::*; 18 | 19 | mod route_intervals; 20 | pub use self::route_intervals::*; 21 | 22 | mod reserved_time; 23 | pub use self::reserved_time::*; 24 | 25 | mod schedule_update; 26 | pub use self::schedule_update::*; 27 | 28 | mod travel_info; 29 | pub use self::travel_info::*; 30 | 31 | mod typed_actor_group_key; 32 | pub use self::typed_actor_group_key::*; 33 | -------------------------------------------------------------------------------- /vrp-core/src/construction/enablers/only_vehicle_activity_cost.rs: -------------------------------------------------------------------------------- 1 | use crate::models::common::{Cost, Timestamp}; 2 | use crate::models::problem::{ActivityCost, SimpleActivityCost}; 3 | use crate::models::solution::Activity; 4 | use crate::models::solution::Route; 5 | 6 | /// Uses costs only for a vehicle ignoring costs of a driver. 7 | #[derive(Default)] 8 | pub struct OnlyVehicleActivityCost { 9 | inner: SimpleActivityCost, 10 | } 11 | 12 | impl ActivityCost for OnlyVehicleActivityCost { 13 | fn cost(&self, route: &Route, activity: &Activity, arrival: Timestamp) -> Cost { 14 | let actor = route.actor.as_ref(); 15 | 16 | let waiting = if activity.place.time.start > arrival { activity.place.time.start - arrival } else { 0.0 }; 17 | let service = activity.place.duration; 18 | 19 | waiting * actor.vehicle.costs.per_waiting_time + service * actor.vehicle.costs.per_service_time 20 | } 21 | 22 | fn estimate_departure(&self, route: &Route, activity: &Activity, arrival: Timestamp) -> Timestamp { 23 | self.inner.estimate_departure(route, activity, arrival) 24 | } 25 | 26 | fn estimate_arrival(&self, route: &Route, activity: &Activity, departure: Timestamp) -> Timestamp { 27 | self.inner.estimate_arrival(route, activity, departure) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /vrp-core/src/construction/enablers/typed_actor_group_key.rs: -------------------------------------------------------------------------------- 1 | use crate::models::problem::Actor; 2 | use std::collections::{HashMap, HashSet}; 3 | use std::sync::Arc; 4 | 5 | /// An actor group key implementation which creates groups using "type" dimension. 6 | pub fn create_typed_actor_groups( 7 | actors: &[Arc], 8 | actor_type_fn: F, 9 | ) -> impl Fn(&Actor) -> usize + Send + Sync + use 10 | where 11 | F: Fn(&Actor) -> String, 12 | { 13 | let unique_type_keys: HashSet<_> = actors.iter().map(|a| (actor_type_fn(a.as_ref()), a.detail.clone())).collect(); 14 | 15 | let type_key_map: HashMap<_, _> = unique_type_keys.into_iter().zip(0_usize..).collect(); 16 | 17 | let groups: HashMap<_, _> = actors 18 | .iter() 19 | .map(|a| (a.clone(), *type_key_map.get(&(actor_type_fn(a.as_ref()), a.detail.clone())).unwrap())) 20 | .collect(); 21 | 22 | move |a| *groups.get(a).unwrap() 23 | } 24 | -------------------------------------------------------------------------------- /vrp-core/src/construction/heuristics/mod.rs: -------------------------------------------------------------------------------- 1 | //! A generalized insertion heuristic implementation. 2 | //! 3 | //! # Design 4 | //! 5 | 6 | mod context; 7 | pub use self::context::*; 8 | 9 | mod evaluators; 10 | pub use self::evaluators::*; 11 | 12 | mod factories; 13 | 14 | mod insertions; 15 | pub use self::insertions::*; 16 | 17 | mod metrics; 18 | pub use self::metrics::*; 19 | 20 | mod selectors; 21 | pub use self::selectors::*; 22 | -------------------------------------------------------------------------------- /vrp-core/src/construction/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains building blocks for constructive heuristics. 2 | //! 3 | //! # Insertion heuristic 4 | //! 5 | //! Insertion heuristic is a popular method to find quickly a **feasible** solution, but without a 6 | //! guarantee of good quality. Essentially, it constructs the solution by repeatedly inserting an 7 | //! unrouted customer into a partially constructed route or as a first customer in an additional 8 | //! route. 9 | //! 10 | 11 | pub mod clustering; 12 | pub mod enablers; 13 | pub mod features; 14 | pub mod heuristics; 15 | pub mod probing; 16 | -------------------------------------------------------------------------------- /vrp-core/src/construction/probing/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module responsible for functionality needed to restore feasible solution from infeasible one. 2 | 3 | mod repair_solution; 4 | pub use self::repair_solution::*; 5 | -------------------------------------------------------------------------------- /vrp-core/src/models/common/dimens.rs: -------------------------------------------------------------------------------- 1 | use rustc_hash::FxHasher; 2 | use std::any::{Any, TypeId}; 3 | use std::collections::HashMap; 4 | use std::hash::BuildHasherDefault; 5 | use std::sync::Arc; 6 | 7 | /// Multiple named dimensions which can contain anything: 8 | /// * unit of measure, e.g. volume, mass, size, etc. 9 | /// * set of skills 10 | /// * tag. 11 | #[derive(Clone, Debug, Default)] 12 | pub struct Dimensions { 13 | index: HashMap, BuildHasherDefault>, 14 | } 15 | 16 | impl Dimensions { 17 | /// Gets a value using key type provided. 18 | pub fn get_value(&self) -> Option<&V> { 19 | self.index.get(&TypeId::of::()).and_then(|any| any.downcast_ref::()) 20 | } 21 | 22 | /// Sets the value using key type provided. 23 | pub fn set_value(&mut self, value: V) { 24 | self.index.insert(TypeId::of::(), Arc::new(value)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /vrp-core/src/models/common/mod.rs: -------------------------------------------------------------------------------- 1 | //! Common models. 2 | 3 | mod dimens; 4 | pub use self::dimens::*; 5 | 6 | mod domain; 7 | pub use self::domain::*; 8 | 9 | mod footprint; 10 | pub use self::footprint::*; 11 | 12 | mod load; 13 | pub use self::load::*; 14 | 15 | mod primitives; 16 | pub use self::primitives::*; 17 | -------------------------------------------------------------------------------- /vrp-core/src/models/common/primitives.rs: -------------------------------------------------------------------------------- 1 | use rosomaxa::prelude::Float; 2 | 3 | /// Represents a time duration. 4 | pub type Duration = Float; 5 | 6 | /// Represents a timestamp. 7 | pub type Timestamp = Float; 8 | 9 | /// Represents a distance. 10 | pub type Distance = Float; 11 | -------------------------------------------------------------------------------- /vrp-core/src/models/extras.rs: -------------------------------------------------------------------------------- 1 | use rustc_hash::FxHasher; 2 | use std::any::{Any, TypeId}; 3 | use std::collections::HashMap; 4 | use std::hash::BuildHasherDefault; 5 | use std::sync::Arc; 6 | 7 | /// Specifies a type used to store any values regarding problem configuration. 8 | #[derive(Clone, Debug, Default)] 9 | pub struct Extras { 10 | index: HashMap, BuildHasherDefault>, 11 | } 12 | 13 | impl Extras { 14 | /// Gets a shared reference to the value from extras using the key type provided. 15 | pub fn get_value(&self) -> Option> { 16 | self.index.get(&TypeId::of::()).cloned().and_then(|any| any.downcast::().ok()) 17 | } 18 | 19 | /// Sets the value, passed as shared reference, to extras using key type provided. 20 | pub fn set_value(&mut self, value: Arc) { 21 | self.index.insert(TypeId::of::(), value); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /vrp-core/src/models/mod.rs: -------------------------------------------------------------------------------- 1 | //! A collection of models to represent problem and solution in Vehicle Routing Problem domain. 2 | 3 | pub(crate) const OP_START_MSG: &str = "Optional start is not yet implemented."; 4 | 5 | mod domain; 6 | pub use self::domain::*; 7 | 8 | mod extras; 9 | pub use self::extras::*; 10 | 11 | mod goal; 12 | pub use self::goal::*; 13 | 14 | pub mod common; 15 | #[doc(hidden)] 16 | pub mod examples; 17 | pub mod problem; 18 | pub mod solution; 19 | -------------------------------------------------------------------------------- /vrp-core/src/models/problem/mod.rs: -------------------------------------------------------------------------------- 1 | //! Problem domain models. 2 | 3 | mod builders; 4 | pub use self::builders::*; 5 | 6 | mod costs; 7 | pub use self::costs::*; 8 | 9 | mod fleet; 10 | pub use self::fleet::*; 11 | 12 | mod jobs; 13 | pub use self::jobs::*; 14 | -------------------------------------------------------------------------------- /vrp-core/src/models/solution/mod.rs: -------------------------------------------------------------------------------- 1 | //! Solution domain models. 2 | 3 | mod route; 4 | pub use self::route::{Activity, Commute, CommuteInfo, Place, Route}; 5 | 6 | mod registry; 7 | pub use self::registry::Registry; 8 | 9 | mod tour; 10 | pub use self::tour::{Leg, Tour}; 11 | -------------------------------------------------------------------------------- /vrp-core/src/prelude.rs: -------------------------------------------------------------------------------- 1 | //! This module reimports commonly used types. 2 | 3 | // Reimport core types 4 | 5 | pub use crate::construction::{ 6 | features::{CapacityFeatureBuilder, MinimizeUnassignedBuilder, TransportFeatureBuilder}, 7 | heuristics::{InsertionContext, MoveContext, RouteContext, RouteState, SolutionContext, SolutionState}, 8 | }; 9 | pub use crate::solver::{Solver, VrpConfigBuilder}; 10 | pub use crate::{ 11 | custom_activity_state, custom_dimension, custom_extra_property, custom_solution_state, custom_tour_state, 12 | }; 13 | 14 | pub use crate::models::{ 15 | common::{Cost, Demand, Dimensions, Location, SingleDimLoad}, 16 | problem::{ 17 | ActivityCost, Fleet, Job, Jobs, MultiBuilder, SimpleTransportCost, SingleBuilder, TransportCost, Vehicle, 18 | VehicleBuilder, VehicleDetailBuilder, 19 | }, 20 | {ConstraintViolation, Feature, FeatureBuilder, FeatureConstraint, FeatureObjective, FeatureState, ViolationCode}, 21 | {Extras, GoalContext, GoalContextBuilder, Problem, ProblemBuilder, Solution}, 22 | }; 23 | 24 | // Reimport rosomaxa types 25 | pub use rosomaxa::{ 26 | evolution::EvolutionConfigBuilder, 27 | utils::{DefaultRandom, Environment, Float, GenericError, GenericResult, InfoLogger, Random}, 28 | }; 29 | -------------------------------------------------------------------------------- /vrp-core/src/solver/processing/advance_departure.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::construction::enablers::advance_departure_time; 3 | use crate::construction::heuristics::InsertionContext; 4 | use rosomaxa::HeuristicSolution; 5 | 6 | /// Provides way to reduce waiting time by advancing departure time. 7 | #[derive(Default)] 8 | pub struct AdvanceDeparture {} 9 | 10 | impl HeuristicSolutionProcessing for AdvanceDeparture { 11 | type Solution = InsertionContext; 12 | 13 | fn post_process(&self, solution: Self::Solution) -> Self::Solution { 14 | let mut insertion_ctx = solution.deep_copy(); 15 | 16 | let problem = insertion_ctx.problem.clone(); 17 | 18 | let activity = problem.activity.as_ref(); 19 | let transport = problem.transport.as_ref(); 20 | 21 | insertion_ctx.solution.routes.iter_mut().for_each(|route_ctx| { 22 | advance_departure_time(route_ctx, activity, transport, true); 23 | }); 24 | 25 | problem.goal.accept_solution_state(&mut insertion_ctx.solution); 26 | 27 | insertion_ctx 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /vrp-core/src/solver/processing/mod.rs: -------------------------------------------------------------------------------- 1 | //! Contains pre and post processing logic. 2 | 3 | use crate::construction::heuristics::InsertionContext; 4 | use rosomaxa::prelude::*; 5 | 6 | mod advance_departure; 7 | pub use self::advance_departure::AdvanceDeparture; 8 | 9 | mod reschedule_reserved_time; 10 | pub use self::reschedule_reserved_time::{RescheduleReservedTime, ReservedTimesExtraProperty}; 11 | 12 | mod unassignment_reason; 13 | pub use self::unassignment_reason::UnassignmentReason; 14 | 15 | mod vicinity_clustering; 16 | pub use self::vicinity_clustering::*; 17 | -------------------------------------------------------------------------------- /vrp-core/src/solver/search/local_search.rs: -------------------------------------------------------------------------------- 1 | use crate::construction::heuristics::InsertionContext; 2 | use crate::models::GoalContext; 3 | use crate::solver::RefinementContext; 4 | use crate::solver::search::LocalOperator; 5 | use rosomaxa::prelude::*; 6 | use std::sync::Arc; 7 | 8 | /// A mutation operator which applies local search principles. 9 | pub struct LocalSearch { 10 | operator: Arc, 11 | } 12 | 13 | impl LocalSearch { 14 | /// Creates a new instance of `LocalSearch`. 15 | pub fn new(operator: Arc) -> Self { 16 | Self { operator } 17 | } 18 | } 19 | 20 | impl HeuristicSearchOperator for LocalSearch { 21 | type Context = RefinementContext; 22 | type Objective = GoalContext; 23 | type Solution = InsertionContext; 24 | 25 | fn search(&self, heuristic_ctx: &Self::Context, solution: &Self::Solution) -> Self::Solution { 26 | let refinement_ctx = heuristic_ctx; 27 | let insertion_ctx = solution; 28 | 29 | match self.operator.explore(refinement_ctx, insertion_ctx) { 30 | Some(new_insertion_ctx) => new_insertion_ctx, 31 | _ => insertion_ctx.deep_copy(), 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /vrp-core/src/solver/search/recreate/recreate_with_cheapest.rs: -------------------------------------------------------------------------------- 1 | use crate::construction::heuristics::InsertionContext; 2 | use crate::construction::heuristics::*; 3 | use crate::solver::RefinementContext; 4 | use crate::solver::search::ConfigurableRecreate; 5 | use crate::solver::search::recreate::Recreate; 6 | use rosomaxa::prelude::Random; 7 | use std::sync::Arc; 8 | 9 | /// A recreate method which is equivalent to cheapest insertion heuristic. 10 | pub struct RecreateWithCheapest { 11 | recreate: ConfigurableRecreate, 12 | } 13 | 14 | impl RecreateWithCheapest { 15 | /// Creates a new instance of `RecreateWithCheapest`. 16 | pub fn new(random: Arc) -> Self { 17 | Self { 18 | recreate: ConfigurableRecreate::new( 19 | Box::::default(), 20 | Box::::default(), 21 | LegSelection::Stochastic(random), 22 | ResultSelection::Concrete(Box::::default()), 23 | Default::default(), 24 | ), 25 | } 26 | } 27 | } 28 | 29 | impl Recreate for RecreateWithCheapest { 30 | fn run(&self, refinement_ctx: &RefinementContext, insertion_ctx: InsertionContext) -> InsertionContext { 31 | self.recreate.run(refinement_ctx, insertion_ctx) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /vrp-core/src/solver/search/recreate/recreate_with_farthest.rs: -------------------------------------------------------------------------------- 1 | use crate::construction::heuristics::InsertionContext; 2 | use crate::construction::heuristics::*; 3 | use crate::solver::RefinementContext; 4 | use crate::solver::search::ConfigurableRecreate; 5 | use crate::solver::search::recreate::Recreate; 6 | use rosomaxa::prelude::Random; 7 | use std::sync::Arc; 8 | 9 | /// A recreate method which always insert first the farthest job in empty route and prefers 10 | /// filling non-empty routes first. 11 | pub struct RecreateWithFarthest { 12 | recreate: ConfigurableRecreate, 13 | } 14 | 15 | impl RecreateWithFarthest { 16 | /// Creates a new instance of `RecreateWithFarthest`. 17 | pub fn new(random: Arc) -> Self { 18 | Self { 19 | recreate: ConfigurableRecreate::new( 20 | Box::::default(), 21 | Box::::default(), 22 | LegSelection::Stochastic(random), 23 | ResultSelection::Concrete(Box::::default()), 24 | Default::default(), 25 | ), 26 | } 27 | } 28 | } 29 | 30 | impl Recreate for RecreateWithFarthest { 31 | fn run(&self, refinement_ctx: &RefinementContext, insertion_ctx: InsertionContext) -> InsertionContext { 32 | self.recreate.run(refinement_ctx, insertion_ctx) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /vrp-core/src/solver/search/recreate/recreate_with_nearest_neighbor.rs: -------------------------------------------------------------------------------- 1 | use crate::construction::heuristics::InsertionContext; 2 | use crate::construction::heuristics::*; 3 | use crate::solver::RefinementContext; 4 | use crate::solver::search::{ConfigurableRecreate, Recreate}; 5 | use rosomaxa::prelude::Random; 6 | use std::sync::Arc; 7 | 8 | /// A recreate strategy which solution using nearest neighbor algorithm. 9 | pub struct RecreateWithNearestNeighbor { 10 | recreate: ConfigurableRecreate, 11 | } 12 | 13 | impl RecreateWithNearestNeighbor { 14 | /// Creates a new instance of `RecreateWithNearestNeighbor`. 15 | pub fn new(random: Arc) -> Self { 16 | Self { 17 | recreate: ConfigurableRecreate::new( 18 | Box::::default(), 19 | Box::::default(), 20 | LegSelection::Stochastic(random), 21 | ResultSelection::Concrete(Box::::default()), 22 | InsertionHeuristic::new(Box::new(PositionInsertionEvaluator::new(InsertionPosition::Last))), 23 | ), 24 | } 25 | } 26 | } 27 | 28 | impl Recreate for RecreateWithNearestNeighbor { 29 | fn run(&self, refinement_ctx: &RefinementContext, insertion_ctx: InsertionContext) -> InsertionContext { 30 | self.recreate.run(refinement_ctx, insertion_ctx) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /vrp-core/src/solver/search/recreate/recreate_with_perturbation.rs: -------------------------------------------------------------------------------- 1 | use crate::construction::heuristics::InsertionContext; 2 | use crate::construction::heuristics::*; 3 | use crate::solver::RefinementContext; 4 | use crate::solver::search::ConfigurableRecreate; 5 | use crate::solver::search::recreate::Recreate; 6 | use rosomaxa::prelude::{Noise, Random}; 7 | use std::sync::Arc; 8 | 9 | /// A recreate method which perturbs the cost by a factor to introduce randomization. 10 | pub struct RecreateWithPerturbation { 11 | recreate: ConfigurableRecreate, 12 | } 13 | 14 | impl RecreateWithPerturbation { 15 | /// Creates a new instance of `RecreateWithPerturbation`. 16 | pub fn new(noise: Noise, random: Arc) -> Self { 17 | Self { 18 | recreate: ConfigurableRecreate::new( 19 | Box::::default(), 20 | Box::::default(), 21 | LegSelection::Stochastic(random.clone()), 22 | ResultSelection::Concrete(Box::new(NoiseResultSelector::new(noise))), 23 | Default::default(), 24 | ), 25 | } 26 | } 27 | 28 | /// Creates a new instance of `RecreateWithPerturbation` with default values. 29 | pub fn new_with_defaults(random: Arc) -> Self { 30 | Self::new(Noise::new_with_ratio(0.05, (-0.25, 0.25), random.clone()), random) 31 | } 32 | } 33 | 34 | impl Recreate for RecreateWithPerturbation { 35 | fn run(&self, refinement_ctx: &RefinementContext, insertion_ctx: InsertionContext) -> InsertionContext { 36 | self.recreate.run(refinement_ctx, insertion_ctx) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /vrp-core/src/solver/search/ruin/random_job_removal.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::construction::heuristics::InsertionContext; 3 | use crate::solver::RefinementContext; 4 | use crate::solver::search::*; 5 | use std::cell::RefCell; 6 | 7 | /// A ruin strategy which removes random jobs from solution. 8 | pub struct RandomJobRemoval { 9 | /// Specifies limits for job removal. 10 | limits: RemovalLimits, 11 | } 12 | 13 | impl RandomJobRemoval { 14 | /// Creates a new instance of `RandomJobRemoval`. 15 | pub fn new(limits: RemovalLimits) -> Self { 16 | Self { limits } 17 | } 18 | } 19 | 20 | impl Ruin for RandomJobRemoval { 21 | fn run(&self, _: &RefinementContext, mut insertion_ctx: InsertionContext) -> InsertionContext { 22 | if insertion_ctx.solution.routes.is_empty() { 23 | return insertion_ctx; 24 | } 25 | 26 | let tracker = RefCell::new(JobRemovalTracker::new(&self.limits, insertion_ctx.environment.random.as_ref())); 27 | let mut tabu_list = TabuList::from(&insertion_ctx); 28 | 29 | (0..self.limits.removed_activities_range.end).take_while(|_| !tracker.borrow().is_limit()).for_each(|_| { 30 | if let Some((_, route_idx, job)) = select_seed_job_with_tabu_list(&insertion_ctx, &tabu_list) { 31 | if tracker.borrow_mut().try_remove_job(&mut insertion_ctx.solution, route_idx, &job) { 32 | tabu_list.add_job(job); 33 | tabu_list.add_actor(insertion_ctx.solution.routes[route_idx].route().actor.clone()); 34 | } 35 | } 36 | }); 37 | 38 | tabu_list.inject(&mut insertion_ctx); 39 | 40 | insertion_ctx 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /vrp-core/src/solver/search/ruin_recreate.rs: -------------------------------------------------------------------------------- 1 | //! Contains a mutation operator based on ruin and recreate principle. 2 | use super::*; 3 | use crate::construction::heuristics::finalize_insertion_ctx; 4 | use crate::models::GoalContext; 5 | use rosomaxa::HeuristicSolution; 6 | use std::sync::Arc; 7 | 8 | /// A mutation operator based on ruin and recreate principle. 9 | pub struct RuinAndRecreate { 10 | ruin: Arc, 11 | recreate: Arc, 12 | } 13 | 14 | impl RuinAndRecreate { 15 | /// Creates a new instance of `RuinAndRecreate` using given ruin and recreate methods. 16 | pub fn new(ruin: Arc, recreate: Arc) -> Self { 17 | Self { ruin, recreate } 18 | } 19 | } 20 | 21 | impl HeuristicSearchOperator for RuinAndRecreate { 22 | type Context = RefinementContext; 23 | type Objective = GoalContext; 24 | type Solution = InsertionContext; 25 | 26 | fn search(&self, heuristic_ctx: &Self::Context, solution: &Self::Solution) -> Self::Solution { 27 | let refinement_ctx = heuristic_ctx; 28 | let insertion_ctx = solution; 29 | 30 | let mut insertion_ctx = 31 | self.recreate.run(refinement_ctx, self.ruin.run(refinement_ctx, insertion_ctx.deep_copy())); 32 | 33 | finalize_insertion_ctx(&mut insertion_ctx); 34 | 35 | insertion_ctx 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /vrp-core/src/solver/search/utils/mod.rs: -------------------------------------------------------------------------------- 1 | mod removal; 2 | pub(crate) use self::removal::*; 3 | 4 | mod selection; 5 | pub(crate) use self::selection::*; 6 | 7 | mod tabu_list; 8 | pub(crate) use self::tabu_list::TabuList; 9 | 10 | mod termination; 11 | pub(crate) use self::termination::*; 12 | -------------------------------------------------------------------------------- /vrp-core/src/solver/search/utils/termination.rs: -------------------------------------------------------------------------------- 1 | use rosomaxa::prelude::*; 2 | use rosomaxa::utils::*; 3 | use std::sync::Arc; 4 | 5 | struct CompositeTimeQuota { 6 | inner: Arc, 7 | limit: usize, 8 | timer: Timer, 9 | } 10 | 11 | impl Quota for CompositeTimeQuota { 12 | fn is_reached(&self) -> bool { 13 | self.timer.elapsed_millis() > self.limit as u128 || self.inner.is_reached() 14 | } 15 | } 16 | 17 | /// Creates a new environment with extra limit quota. Limit is specified in seconds. 18 | pub fn create_environment_with_custom_quota(limit: Option, environment: &Environment) -> Arc { 19 | Arc::new(Environment { 20 | quota: match (limit, environment.quota.clone()) { 21 | (Some(limit), None) => Some(Arc::new(TimeQuota::new(limit as Float / 1000.))), 22 | (None, Some(quota)) => Some(quota), 23 | (Some(limit), Some(inner)) => Some(Arc::new(CompositeTimeQuota { inner, limit, timer: Timer::start() })), 24 | (None, None) => None, 25 | }, 26 | random: environment.random.clone(), 27 | parallelism: environment.parallelism.clone(), 28 | logger: environment.logger.clone(), 29 | is_experimental: environment.is_experimental, 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /vrp-core/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | //! A collection of various utility helpers. 2 | 3 | // Reimport rosomaxa utils 4 | pub use rosomaxa::utils::*; 5 | 6 | mod types; 7 | pub use self::types::Either; 8 | -------------------------------------------------------------------------------- /vrp-core/src/utils/types.rs: -------------------------------------------------------------------------------- 1 | /// Represents a type with two values. 2 | pub enum Either { 3 | /// Left value. 4 | Left(L), 5 | /// Right value. 6 | Right(R), 7 | } 8 | 9 | impl Either { 10 | /// Checks whether it is left variant. 11 | pub fn is_left(&self) -> bool { 12 | matches!(self, Self::Left(_)) 13 | } 14 | } 15 | 16 | impl Clone for Either 17 | where 18 | L: Clone, 19 | R: Clone, 20 | { 21 | fn clone(&self) -> Self { 22 | match self { 23 | Either::Left(left) => Either::Left(left.clone()), 24 | Either::Right(right) => Either::Right(right.clone()), 25 | } 26 | } 27 | } 28 | 29 | impl Iterator for Either 30 | where 31 | L: Iterator, 32 | R: Iterator, 33 | { 34 | type Item = T; 35 | fn next(&mut self) -> Option { 36 | match self { 37 | Self::Left(it) => it.next(), 38 | Self::Right(it) => it.next(), 39 | } 40 | } 41 | 42 | fn size_hint(&self) -> (usize, Option) { 43 | match self { 44 | Self::Left(it) => it.size_hint(), 45 | Self::Right(it) => it.size_hint(), 46 | } 47 | } 48 | 49 | fn nth(&mut self, n: usize) -> Option { 50 | match self { 51 | Self::Left(it) => it.nth(n), 52 | Self::Right(it) => it.nth(n), 53 | } 54 | } 55 | 56 | fn fold(self, init: B, f: F) -> B 57 | where 58 | F: FnMut(B, Self::Item) -> B, 59 | { 60 | match self { 61 | Self::Left(it) => it.fold(init, f), 62 | Self::Right(it) => it.fold(init, f), 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /vrp-core/tests/helpers/construction/clustering/dbscan.rs: -------------------------------------------------------------------------------- 1 | use crate::helpers::construction::clustering::p; 2 | use crate::helpers::solver::generate_matrix_distances_from_points; 3 | use rosomaxa::prelude::Float; 4 | 5 | pub fn create_test_distances() -> Vec { 6 | generate_matrix_distances_from_points(&[ 7 | p(0., 0.), // A 8 | p(5., 5.), 9 | p(0., 10.), 10 | p(5., 15.), 11 | p(200., 200.), // Ghost 12 | p(25., 0.), // B 13 | p(30., 5.), 14 | p(30., 10.), 15 | ]) 16 | } 17 | -------------------------------------------------------------------------------- /vrp-core/tests/helpers/construction/clustering/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod dbscan; 2 | pub mod vicinity; 3 | 4 | use crate::algorithms::geometry::Point; 5 | use rosomaxa::prelude::Float; 6 | 7 | pub fn p(x: Float, y: Float) -> Point { 8 | Point { x, y } 9 | } 10 | -------------------------------------------------------------------------------- /vrp-core/tests/helpers/construction/features.rs: -------------------------------------------------------------------------------- 1 | use crate::models::common::{Demand, MultiDimLoad, SingleDimLoad}; 2 | 3 | pub fn create_simple_demand(size: i32) -> Demand { 4 | if size > 0 { 5 | Demand:: { 6 | pickup: (SingleDimLoad::new(size), SingleDimLoad::default()), 7 | delivery: (SingleDimLoad::default(), SingleDimLoad::default()), 8 | } 9 | } else { 10 | Demand:: { 11 | pickup: (SingleDimLoad::default(), SingleDimLoad::default()), 12 | delivery: (SingleDimLoad::new(-size), SingleDimLoad::default()), 13 | } 14 | } 15 | } 16 | 17 | pub fn create_simple_dynamic_demand(size: i32) -> Demand { 18 | if size > 0 { 19 | Demand:: { 20 | pickup: (SingleDimLoad::default(), SingleDimLoad::new(size)), 21 | delivery: (SingleDimLoad::default(), SingleDimLoad::default()), 22 | } 23 | } else { 24 | Demand:: { 25 | pickup: (SingleDimLoad::default(), SingleDimLoad::default()), 26 | delivery: (SingleDimLoad::default(), SingleDimLoad::new(-size)), 27 | } 28 | } 29 | } 30 | 31 | pub fn single_demand_as_multi(pickup: (i32, i32), delivery: (i32, i32)) -> Demand { 32 | let make = |value| { 33 | if value == 0 { MultiDimLoad::default() } else { MultiDimLoad::new(vec![value]) } 34 | }; 35 | 36 | Demand { pickup: (make(pickup.0), make(pickup.1)), delivery: (make(delivery.0), make(delivery.1)) } 37 | } 38 | -------------------------------------------------------------------------------- /vrp-core/tests/helpers/construction/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod clustering; 2 | pub mod features; 3 | pub mod heuristics; 4 | -------------------------------------------------------------------------------- /vrp-core/tests/helpers/macros.rs: -------------------------------------------------------------------------------- 1 | // See https://stackoverflow.com/questions/34662713/how-can-i-create-parameterized-tests-in-rust 2 | macro_rules! with_dollar_sign { 3 | ($($body:tt)*) => { 4 | macro_rules! __with_dollar_sign { $($body)* } 5 | __with_dollar_sign!($); 6 | } 7 | } 8 | 9 | /// A macro to a create parameterized test. 10 | #[macro_export] 11 | macro_rules! parameterized_test { 12 | ($name:ident, $args:pat, $body:tt) => { 13 | with_dollar_sign! { 14 | ($d:tt) => { 15 | macro_rules! $name { 16 | ($d($d pname:ident: $d values:expr_2021,)*) => { 17 | mod $name { 18 | use super::*; 19 | $d( 20 | #[test] 21 | fn $d pname() { 22 | let $args = $d values; 23 | $body 24 | } 25 | )* 26 | }}}}} 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /vrp-core/tests/helpers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod construction; 2 | pub mod models; 3 | pub mod solver; 4 | pub mod utils; 5 | 6 | #[cfg(test)] 7 | #[macro_use] 8 | pub mod macros; 9 | -------------------------------------------------------------------------------- /vrp-core/tests/helpers/models/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod domain; 2 | pub mod problem; 3 | pub mod solution; 4 | -------------------------------------------------------------------------------- /vrp-core/tests/helpers/models/problem/mod.rs: -------------------------------------------------------------------------------- 1 | mod costs; 2 | pub use self::costs::*; 3 | 4 | mod fleet; 5 | pub use self::fleet::*; 6 | 7 | mod jobs; 8 | pub use self::jobs::*; 9 | -------------------------------------------------------------------------------- /vrp-core/tests/helpers/models/solution/actor.rs: -------------------------------------------------------------------------------- 1 | use crate::helpers::models::domain::test_random; 2 | use crate::helpers::models::problem::*; 3 | use crate::models::common::TimeInterval; 4 | use crate::models::problem::{Actor, ActorDetail, VehiclePlace}; 5 | use crate::models::solution::Registry; 6 | use std::sync::Arc; 7 | 8 | pub fn test_actor() -> Arc { 9 | test_actor_with_profile(0) 10 | } 11 | 12 | pub fn test_actor_with_profile(profile_idx: usize) -> Arc { 13 | Arc::new(Actor { 14 | vehicle: Arc::new(test_vehicle(profile_idx)), 15 | driver: Arc::new(test_driver()), 16 | detail: ActorDetail { 17 | start: Some(VehiclePlace { 18 | location: DEFAULT_ACTOR_LOCATION, 19 | time: TimeInterval { earliest: Some(DEFAULT_ACTOR_TIME_WINDOW.start), latest: None }, 20 | }), 21 | end: Some(VehiclePlace { 22 | location: DEFAULT_ACTOR_LOCATION, 23 | time: TimeInterval { earliest: None, latest: Some(DEFAULT_ACTOR_TIME_WINDOW.end) }, 24 | }), 25 | time: DEFAULT_ACTOR_TIME_WINDOW, 26 | }, 27 | }) 28 | } 29 | 30 | pub fn create_test_registry() -> Registry { 31 | let fleet = FleetBuilder::default() 32 | .add_driver(test_driver_with_costs(empty_costs())) 33 | .add_vehicle(TestVehicleBuilder::default().id("v1").build()) 34 | .build(); 35 | Registry::new(&fleet, test_random()) 36 | } 37 | -------------------------------------------------------------------------------- /vrp-core/tests/helpers/models/solution/mod.rs: -------------------------------------------------------------------------------- 1 | mod actor; 2 | pub use self::actor::*; 3 | 4 | mod route; 5 | pub use self::route::*; 6 | -------------------------------------------------------------------------------- /vrp-core/tests/helpers/utils/mod.rs: -------------------------------------------------------------------------------- 1 | use rosomaxa::utils::{Environment, Random}; 2 | use std::sync::Arc; 3 | 4 | pub mod random; 5 | 6 | pub fn create_test_environment_with_random(random: Arc) -> Arc { 7 | Arc::new(Environment { random, ..Default::default() }) 8 | } 9 | -------------------------------------------------------------------------------- /vrp-core/tests/helpers/utils/random.rs: -------------------------------------------------------------------------------- 1 | use rosomaxa::prelude::{Float, Random, RandomGen}; 2 | use std::sync::RwLock; 3 | 4 | struct FakeDistribution { 5 | values: Vec, 6 | } 7 | 8 | impl FakeDistribution { 9 | pub fn new(values: Vec) -> Self { 10 | let mut values = values; 11 | values.reverse(); 12 | Self { values } 13 | } 14 | 15 | pub fn next(&mut self) -> T { 16 | self.values.pop().unwrap() 17 | } 18 | } 19 | 20 | pub struct FakeRandom { 21 | ints: RwLock>, 22 | reals: RwLock>, 23 | } 24 | 25 | impl FakeRandom { 26 | pub fn new(ints: Vec, reals: Vec) -> Self { 27 | Self { ints: RwLock::new(FakeDistribution::new(ints)), reals: RwLock::new(FakeDistribution::new(reals)) } 28 | } 29 | } 30 | 31 | impl Random for FakeRandom { 32 | fn uniform_int(&self, min: i32, max: i32) -> i32 { 33 | assert!(min <= max); 34 | self.ints.write().unwrap().next() 35 | } 36 | 37 | fn uniform_real(&self, min: Float, max: Float) -> Float { 38 | assert!(min < max); 39 | self.reals.write().unwrap().next() 40 | } 41 | 42 | fn is_head_not_tails(&self) -> bool { 43 | self.uniform_int(1, 2) == 1 44 | } 45 | 46 | fn is_hit(&self, probability: Float) -> bool { 47 | self.uniform_real(0., 1.) < probability 48 | } 49 | 50 | fn weighted(&self, _: &[usize]) -> usize { 51 | todo!() 52 | } 53 | 54 | fn get_rng(&self) -> RandomGen { 55 | RandomGen::new_repeatable() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /vrp-core/tests/unit/algorithms/geometry/point_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | fn round(value: Float) -> Float { 4 | (value * 1000.).round() / 1000. 5 | } 6 | 7 | #[test] 8 | pub fn can_calculate_distance_between_points() { 9 | let a = Point::new(3., 2.); 10 | let b = Point::new(9., 7.); 11 | 12 | assert_eq!(round(a.distance_to_point(&b)), 7.81); 13 | } 14 | 15 | #[test] 16 | pub fn can_calculate_distance_to_line() { 17 | let a = Point::new(0., 2.); 18 | let b = Point::new(5., 8.); 19 | let c = Point::new(-3., 7.); 20 | 21 | assert_eq!(round(c.distance_to_line(&a, &b)), 5.506); 22 | } 23 | 24 | #[test] 25 | pub fn can_calculate_distance_to_segment() { 26 | let a = Point::new(0., 0.); 27 | let b = Point::new(1., 0.); 28 | let c = Point::new(4., 2.); 29 | 30 | assert_eq!(round(c.distance_to_segment(&a, &b)), round(Float::sqrt(13.))); 31 | } 32 | -------------------------------------------------------------------------------- /vrp-core/tests/unit/algorithms/lkh/tour_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | fn get_start() -> Path { 4 | vec![0, 2, 1, 3] 5 | } 6 | 7 | fn edge_set(edges: &[(usize, usize)]) -> EdgeSet { 8 | edges.iter().copied().collect() 9 | } 10 | 11 | #[test] 12 | fn test_tour_creation() { 13 | let t = Tour::new(get_start()); 14 | 15 | // NOTE edges always reordered 16 | assert_eq!(t.edges, edge_set(&[(0, 2), (1, 2), (1, 3), (0, 3)])); 17 | assert_eq!(t.len(), 4); 18 | } 19 | 20 | #[test] 21 | fn test_optimal_2opt() { 22 | let t = Tour::new([0, 2, 1, 3]); 23 | let broken = edge_set(&[(0, 2), (1, 3)]); 24 | let joined = edge_set(&[(0, 1), (2, 3)]); 25 | 26 | let new_tour = t.try_path(&broken, &joined).expect("2-opt failed"); 27 | 28 | assert_eq!(new_tour, [0, 1, 2, 3]); 29 | } 30 | 31 | #[test] 32 | fn test_optimal_3opt() { 33 | let t = Tour::new([0, 3, 2, 4, 5, 1]); 34 | let broken = edge_set(&[(0, 3), (2, 4), (1, 5)]); 35 | let joined = edge_set(&[(0, 5), (3, 4), (1, 2)]); 36 | 37 | let new_tour = t.try_path(&broken, &joined).expect("3-opt failed"); 38 | 39 | assert_eq!(new_tour, [0, 1, 2, 3, 4, 5]); 40 | } 41 | 42 | #[test] 43 | fn test_disjoint_path() { 44 | let t = Tour::new([0, 3, 2, 4, 5, 1]); 45 | let broken = edge_set(&[(0, 3), (4, 5)]); 46 | let joined = edge_set(&[(0, 5), (3, 4)]); 47 | 48 | let new_tour = t.try_path(&broken, &joined); 49 | 50 | assert!(new_tour.is_none()); 51 | } 52 | -------------------------------------------------------------------------------- /vrp-core/tests/unit/algorithms/structures/bitvec_test.rs: -------------------------------------------------------------------------------- 1 | use super::BitVec; 2 | 3 | #[test] 4 | fn can_create_new() { 5 | let bitvec = BitVec::new(10); 6 | assert_eq!(bitvec.len(), 10); 7 | assert!(bitvec.blocks.iter().all(|&block| block == 0)); 8 | } 9 | 10 | #[test] 11 | fn can_use_set_and_get() { 12 | let mut bitvec = BitVec::new(10); 13 | bitvec.set(3, true); 14 | assert_eq!(bitvec.get(3), Some(true)); 15 | bitvec.set(3, false); 16 | assert_eq!(bitvec.get(3), Some(false)); 17 | } 18 | 19 | #[test] 20 | #[should_panic] 21 | fn can_panic_when_set_out_of_bounds() { 22 | let mut bitvec = BitVec::new(10); 23 | bitvec.set(10, true); 24 | } 25 | 26 | #[test] 27 | fn can_use_union() { 28 | let mut bitvec1 = BitVec::new(10); 29 | let mut bitvec2 = BitVec::new(10); 30 | bitvec1.set(3, true); 31 | bitvec2.set(4, true); 32 | bitvec1.union(&bitvec2); 33 | assert_eq!(bitvec1.get(3), Some(true)); 34 | assert_eq!(bitvec1.get(4), Some(true)); 35 | } 36 | 37 | #[test] 38 | fn can_use_len_and_is_empty() { 39 | let bitvec = BitVec::new(0); 40 | assert_eq!(bitvec.len(), 0); 41 | assert!(bitvec.is_empty()); 42 | } 43 | 44 | #[test] 45 | fn can_index() { 46 | let mut bitvec = BitVec::new(10); 47 | bitvec.set(3, true); 48 | assert!(bitvec[3]); 49 | assert!(!bitvec[4]); 50 | } 51 | 52 | #[test] 53 | fn can_use_display() { 54 | let mut bitvec = BitVec::new(5); 55 | bitvec.set(0, true); 56 | bitvec.set(2, true); 57 | assert_eq!(format!("{}", bitvec), "[10100]"); 58 | } 59 | -------------------------------------------------------------------------------- /vrp-core/tests/unit/construction/clustering/kmedoids/multi_tier_clusters_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::models::common::{Distance, Duration}; 3 | use crate::models::problem::TravelTime; 4 | use crate::models::solution::Route; 5 | use std::collections::HashSet; 6 | 7 | struct MockTransportCost; 8 | impl TransportCost for MockTransportCost { 9 | fn duration_approx(&self, _: &Profile, _: Location, _: Location) -> Duration { 10 | todo!() 11 | } 12 | 13 | fn distance_approx(&self, _profile: &Profile, from: Location, to: Location) -> f64 { 14 | (from as f64 - to as f64).abs() 15 | } 16 | 17 | fn duration(&self, _: &Route, _: Location, _: Location, _: TravelTime) -> Duration { 18 | todo!() 19 | } 20 | 21 | fn distance(&self, _: &Route, _: Location, _: Location, _: TravelTime) -> Distance { 22 | todo!() 23 | } 24 | 25 | fn size(&self) -> usize { 26 | 100 27 | } 28 | } 29 | 30 | #[test] 31 | fn can_create_multi_tier_clusters() -> GenericResult<()> { 32 | let profile = Profile::default(); 33 | let transport = MockTransportCost; 34 | let expected_cluster_nums = [2, 3, 4, 5, 8, 10, 12, 16, 32]; 35 | 36 | let clusters = create_multi_tier_clusters(profile, &transport)?; 37 | 38 | assert_eq!(clusters.len(), expected_cluster_nums.len()); 39 | clusters.iter().zip(expected_cluster_nums.iter()).for_each(|(clusters, expected_num)| { 40 | assert_eq!(clusters.len(), *expected_num); 41 | let total = clusters.iter().flat_map(|(_, cluster)| cluster.iter()).collect::>().len(); 42 | assert_eq!(total, transport.size()); 43 | }); 44 | 45 | Ok(()) 46 | } 47 | -------------------------------------------------------------------------------- /vrp-core/tests/unit/construction/features/minimize_unassigned_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::helpers::construction::heuristics::TestInsertionContextBuilder; 3 | use crate::helpers::models::solution::RouteContextBuilder; 4 | 5 | #[test] 6 | fn can_properly_estimate_empty_solution() { 7 | let empty = TestInsertionContextBuilder::default().build(); 8 | let non_empty = 9 | TestInsertionContextBuilder::default().with_routes(vec![RouteContextBuilder::default().build()]).build(); 10 | let objective = MinimizeUnassignedBuilder::new("minimize_unassigned").build().unwrap().objective.unwrap(); 11 | 12 | assert_eq!(objective.fitness(&empty), 0.); 13 | assert_eq!(objective.fitness(&non_empty), 0.); 14 | } 15 | -------------------------------------------------------------------------------- /vrp-core/tests/unit/models/problem/fleet_test.rs: -------------------------------------------------------------------------------- 1 | use crate::helpers::models::problem::{FleetBuilder, test_driver, test_vehicle}; 2 | 3 | #[test] 4 | fn fleet_creates_unique_profiles_from_vehicles() { 5 | let profile1 = 0; 6 | let profile2 = 1; 7 | 8 | assert_eq!( 9 | FleetBuilder::default() 10 | .add_driver(test_driver()) 11 | .add_vehicle(test_vehicle(profile1)) 12 | .add_vehicle(test_vehicle(profile2)) 13 | .add_vehicle(test_vehicle(profile1)) 14 | .build() 15 | .profiles 16 | .iter() 17 | .map(|profile| profile.index) 18 | .collect::>(), 19 | vec![profile1, profile2] 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /vrp-core/tests/unit/solver/search/ruin/worst_jobs_removal_test.rs: -------------------------------------------------------------------------------- 1 | use super::{Ruin, WorstJobRemoval}; 2 | use crate::construction::heuristics::InsertionContext; 3 | use crate::helpers::models::domain::get_sorted_customer_ids_from_jobs; 4 | use crate::helpers::solver::{create_default_refinement_ctx, generate_matrix_routes_with_defaults}; 5 | use crate::helpers::utils::create_test_environment_with_random; 6 | use crate::helpers::utils::random::FakeRandom; 7 | use crate::solver::search::RemovalLimits; 8 | use std::sync::Arc; 9 | 10 | parameterized_test! {can_ruin_solution_with_matrix_routes, (matrix, ints, expected_ids), { 11 | can_ruin_solution_with_matrix_routes_impl(matrix, ints, expected_ids); 12 | }} 13 | 14 | can_ruin_solution_with_matrix_routes! { 15 | case_01: ((5, 3), vec![4, 2, 0, 0, 0], vec!["c3", "c4", "c8", "c9"]), 16 | case_02: ((5, 3), vec![6, 2, 0, 0, 0], vec!["c14", "c2", "c3", "c4", "c8", "c9"]), 17 | } 18 | 19 | fn can_ruin_solution_with_matrix_routes_impl(matrix: (usize, usize), ints: Vec, expected_ids: Vec<&str>) { 20 | let reals = vec![]; 21 | 22 | let (problem, solution) = generate_matrix_routes_with_defaults(matrix.0, matrix.1, false); 23 | let limits = RemovalLimits { removed_activities_range: 10..10, affected_routes_range: 2..2 }; 24 | let insertion_ctx: InsertionContext = InsertionContext::new_from_solution( 25 | Arc::new(problem), 26 | (solution, None), 27 | create_test_environment_with_random(Arc::new(FakeRandom::new(ints, reals))), 28 | ); 29 | 30 | let insertion_ctx = WorstJobRemoval::new(4, limits) 31 | .run(&create_default_refinement_ctx(insertion_ctx.problem.clone()), insertion_ctx); 32 | 33 | assert_eq!(get_sorted_customer_ids_from_jobs(&insertion_ctx.solution.required), expected_ids); 34 | } 35 | -------------------------------------------------------------------------------- /vrp-pragmatic/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vrp-pragmatic" 3 | description = "An extension logic for solving rich VRP" 4 | version.workspace = true 5 | authors.workspace = true 6 | license.workspace = true 7 | keywords.workspace = true 8 | categories.workspace = true 9 | readme.workspace = true 10 | homepage.workspace = true 11 | repository.workspace = true 12 | edition.workspace = true 13 | 14 | [dependencies] 15 | vrp-core.workspace = true 16 | 17 | serde.workspace = true 18 | serde_json.workspace = true 19 | rand.workspace = true 20 | 21 | time = { version = "0.3.41", features = ["parsing", "formatting"] } 22 | paste = "1.0.15" 23 | 24 | [dev-dependencies] 25 | criterion.workspace = true 26 | 27 | proptest = "1.6.0" 28 | uuid = { version = "1.17.0", features = ["v4"] } 29 | 30 | [[bench]] 31 | name = "pragmatic_simple" 32 | harness = false 33 | -------------------------------------------------------------------------------- /vrp-pragmatic/README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | The `vrp-pragmatic` crates aims to solve real world **Vehicle Routing Problem** variations allowing users to specify 4 | their problems via simple `pragmatic` json format. 5 | 6 | Please check [the repository](https://github.com/reinterpretcat/vrp) for more details. -------------------------------------------------------------------------------- /vrp-pragmatic/src/format/dimensions.rs: -------------------------------------------------------------------------------- 1 | //! Specifies different properties as extension points on Dimensions type. 2 | 3 | use vrp_core::construction::features::BreakPolicy; 4 | use vrp_core::custom_dimension; 5 | use vrp_core::models::common::Dimensions; 6 | use vrp_core::utils::Float; 7 | 8 | custom_dimension!(pub VehicleType typeof String); 9 | 10 | custom_dimension!(pub ShiftIndex typeof usize); 11 | 12 | custom_dimension!(pub TourSize typeof usize); 13 | 14 | custom_dimension!(pub PlaceTags typeof Vec<(usize, String)>); 15 | 16 | custom_dimension!(pub JobOrder typeof i32); 17 | 18 | custom_dimension!(pub JobValue typeof Float); 19 | 20 | custom_dimension!(pub JobType typeof String); 21 | 22 | custom_dimension!(pub BreakPolicy typeof BreakPolicy); 23 | -------------------------------------------------------------------------------- /vrp-pragmatic/src/format/location_fallback.rs: -------------------------------------------------------------------------------- 1 | use crate::format::{CoordIndex, CustomLocationType, Location as ApiLocation}; 2 | use std::sync::Arc; 3 | use vrp_core::models::common::{Distance, Duration, Location, Profile}; 4 | use vrp_core::models::problem::TransportFallback; 5 | use vrp_core::prelude::Float; 6 | 7 | /// A transport fallback for only a custom unknown location type. 8 | /// Returns zero distance/duration for unknown type locations. 9 | pub struct UnknownLocationFallback { 10 | coord_index: Arc, 11 | } 12 | 13 | impl UnknownLocationFallback { 14 | /// Creates a new instance of [`UnknownLocationFallback`]; 15 | pub fn new(coord_index: Arc) -> Self { 16 | Self { coord_index } 17 | } 18 | 19 | fn get_default_value(&self, from: Location, to: Location) -> Float { 20 | let (from, to) = (self.coord_index.get_by_idx(from), self.coord_index.get_by_idx(to)); 21 | 22 | match (from, to) { 23 | (Some(ApiLocation::Custom { r#type: CustomLocationType::Unknown }), _) 24 | | (_, Some(ApiLocation::Custom { r#type: CustomLocationType::Unknown })) => Float::default(), 25 | _ => panic!("fallback is only for locations of custom unknown type"), 26 | } 27 | } 28 | } 29 | 30 | impl TransportFallback for UnknownLocationFallback { 31 | fn duration(&self, _: &Profile, from: Location, to: Location) -> Duration { 32 | self.get_default_value(from, to) 33 | } 34 | 35 | fn distance(&self, _: &Profile, from: Location, to: Location) -> Distance { 36 | self.get_default_value(from, to) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /vrp-pragmatic/src/format/solution/extensions.rs: -------------------------------------------------------------------------------- 1 | use crate::format::solution::{Statistic, Timing}; 2 | use std::ops::Add; 3 | 4 | impl Add for Statistic { 5 | type Output = Statistic; 6 | 7 | fn add(self, rhs: Self) -> Self::Output { 8 | Statistic { 9 | cost: self.cost + rhs.cost, 10 | distance: self.distance + rhs.distance, 11 | duration: self.duration + rhs.duration, 12 | times: Timing { 13 | driving: self.times.driving + rhs.times.driving, 14 | serving: self.times.serving + rhs.times.serving, 15 | waiting: self.times.waiting + rhs.times.waiting, 16 | break_time: self.times.break_time + rhs.times.break_time, 17 | commuting: self.times.commuting + rhs.times.commuting, 18 | parking: self.times.parking + rhs.times.parking, 19 | }, 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /vrp-pragmatic/src/utils/collections.rs: -------------------------------------------------------------------------------- 1 | /// Combines error results. 2 | pub fn combine_error_results(results: &[Result<(), T>]) -> Result<(), Vec> { 3 | let errors = results.iter().cloned().flat_map(|result| result.err().into_iter()).collect::>(); 4 | 5 | if errors.is_empty() { Ok(()) } else { Err(errors) } 6 | } 7 | -------------------------------------------------------------------------------- /vrp-pragmatic/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | //! Contains utility logic. 2 | 3 | mod approx_transportation; 4 | pub use self::approx_transportation::*; 5 | 6 | mod collections; 7 | pub use self::collections::*; 8 | 9 | mod permutations; 10 | pub use self::permutations::VariableJobPermutation; 11 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/discovery/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains discovery tests which tries to explore problem space in order to catch some 2 | //! potential issues. Most of the test require a lot of time to run. 3 | 4 | mod property; 5 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/discovery/property/generated_with_reload.rs: -------------------------------------------------------------------------------- 1 | use crate::format::problem::*; 2 | use crate::generator::*; 3 | use crate::helpers::solve_with_metaheuristic_and_iterations; 4 | 5 | use proptest::prelude::*; 6 | 7 | fn get_reloads() -> impl Strategy>> { 8 | prop::collection::vec( 9 | generate_reload( 10 | generate_location(&DEFAULT_BOUNDING_BOX), 11 | generate_durations(300..3600), 12 | generate_no_tags(), 13 | default_job_single_day_time_windows(), 14 | ), 15 | 1..4, 16 | ) 17 | .prop_map(Some) 18 | } 19 | 20 | prop_compose! { 21 | fn get_vehicle_type_with_reloads() 22 | ( 23 | vehicle in default_vehicle_type_prototype(), 24 | reloads in get_reloads() 25 | ) -> VehicleType { 26 | 27 | assert_eq!(vehicle.shifts.len(), 1); 28 | 29 | let mut vehicle = vehicle; 30 | vehicle.shifts.first_mut().unwrap().reloads = reloads; 31 | 32 | vehicle 33 | } 34 | } 35 | 36 | prop_compose! { 37 | fn create_problem_with_reloads() 38 | ( 39 | plan in generate_plan(generate_jobs(default_job_prototype(), 1..256)), 40 | fleet in generate_fleet( 41 | generate_vehicles(get_vehicle_type_with_reloads(), 1..4), 42 | default_matrix_profiles()) 43 | ) -> Problem { 44 | Problem { 45 | plan, 46 | fleet, 47 | objectives: None, 48 | } 49 | } 50 | } 51 | 52 | proptest! { 53 | #![proptest_config(ProptestConfig::with_cases(512))] 54 | #[test] 55 | #[ignore] 56 | fn can_solve_problem_with_reloads(problem in create_problem_with_reloads()) { 57 | solve_with_metaheuristic_and_iterations(problem, None, 10); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/discovery/property/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contain property based tests. 2 | 3 | mod generated_with_breaks; 4 | mod generated_with_clustering; 5 | mod generated_with_groups; 6 | mod generated_with_recharge; 7 | mod generated_with_relations; 8 | mod generated_with_reload; 9 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/breaks/mod.rs: -------------------------------------------------------------------------------- 1 | mod basic_break_test; 2 | mod break_with_multiple_locations; 3 | mod interval_break_test; 4 | mod multi_break_test; 5 | mod open_end_by_interval_break; 6 | mod policy_break_test; 7 | mod relation_break_test; 8 | mod required_break; 9 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/capacity/mod.rs: -------------------------------------------------------------------------------- 1 | mod simple_capacity_test; 2 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/capacity/simple_capacity_test.rs: -------------------------------------------------------------------------------- 1 | use crate::format::problem::*; 2 | use crate::helpers::*; 3 | 4 | #[test] 5 | fn can_schedule_pickup_at_tour_end() { 6 | let problem = Problem { 7 | plan: Plan { 8 | jobs: vec![ 9 | create_delivery_job_with_demand("job1", (1., 0.), vec![1]), 10 | create_delivery_job_with_demand("job2", (2., 0.), vec![1]), 11 | create_pickup_job_with_demand("job3", (3., 0.), vec![2]), 12 | ], 13 | ..create_empty_plan() 14 | }, 15 | fleet: Fleet { 16 | vehicles: vec![VehicleType { capacity: vec![2], ..create_default_vehicle_type() }], 17 | ..create_default_fleet() 18 | }, 19 | ..create_empty_problem() 20 | }; 21 | let matrix = create_matrix_from_problem(&problem); 22 | 23 | let solution = solve_with_cheapest_insertion(problem, Some(vec![matrix])); 24 | 25 | assert!(solution.unassigned.is_none()) 26 | } 27 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/compatibility/mod.rs: -------------------------------------------------------------------------------- 1 | mod basic_compatibility; 2 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/fleet/basic_open_end.rs: -------------------------------------------------------------------------------- 1 | use crate::format::problem::*; 2 | use crate::helpers::*; 3 | 4 | #[test] 5 | fn can_use_vehicle_with_open_end() { 6 | let problem = Problem { 7 | plan: Plan { jobs: vec![create_delivery_job("job1", (1., 0.))], ..create_empty_plan() }, 8 | fleet: Fleet { 9 | vehicles: vec![VehicleType { 10 | shifts: vec![create_default_open_vehicle_shift()], 11 | ..create_default_vehicle_type() 12 | }], 13 | ..create_default_fleet() 14 | }, 15 | ..create_empty_problem() 16 | }; 17 | let matrix = create_matrix_from_problem(&problem); 18 | 19 | let solution = solve_with_metaheuristic(problem, Some(vec![matrix])); 20 | 21 | assert_eq!( 22 | solution, 23 | SolutionBuilder::default() 24 | .tour( 25 | TourBuilder::default() 26 | .stops(vec![ 27 | StopBuilder::default() 28 | .coordinate((0., 0.)) 29 | .schedule_stamp(0., 0.) 30 | .load(vec![1]) 31 | .build_departure(), 32 | StopBuilder::default() 33 | .coordinate((1., 0.)) 34 | .schedule_stamp(1., 2.) 35 | .load(vec![0]) 36 | .distance(1) 37 | .build_single("job1", "delivery"), 38 | ]) 39 | .statistic(StatisticBuilder::default().driving(1).serving(1).build()) 40 | .build() 41 | ) 42 | .build() 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/fleet/mod.rs: -------------------------------------------------------------------------------- 1 | mod basic_multi_shift; 2 | mod basic_open_end; 3 | mod multi_dimens; 4 | mod profile_variation; 5 | mod unreachable_jobs; 6 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/fleet/profile_variation.rs: -------------------------------------------------------------------------------- 1 | use crate::format::problem::*; 2 | use crate::helpers::*; 3 | use vrp_core::prelude::Float; 4 | 5 | fn create_vehicle_type(type_id: &str, scale: Option) -> VehicleType { 6 | VehicleType { 7 | type_id: type_id.to_string(), 8 | profile: VehicleProfile { matrix: "car".to_string(), scale }, 9 | vehicle_ids: vec![format!("{type_id}_1")], 10 | ..create_default_vehicle_type() 11 | } 12 | } 13 | #[test] 14 | fn can_use_scale() { 15 | let problem = Problem { 16 | plan: Plan { jobs: vec![create_delivery_job("job1", (10., 0.))], ..create_empty_plan() }, 17 | fleet: Fleet { 18 | vehicles: vec![create_vehicle_type("normal", None), create_vehicle_type("slow", Some(0.5))], 19 | ..create_default_fleet() 20 | }, 21 | ..create_empty_problem() 22 | }; 23 | let matrix = create_matrix_from_problem(&problem); 24 | 25 | let solution = solve_with_metaheuristic(problem, Some(vec![matrix])); 26 | 27 | assert!(solution.unassigned.is_none()); 28 | assert_eq!(solution.tours.len(), 1); 29 | let tour = solution.tours.first().unwrap(); 30 | assert_eq!(tour.vehicle_id, "slow_1"); 31 | assert_eq!(tour.statistic.distance, 20); 32 | assert_eq!(tour.statistic.duration, 11) 33 | } 34 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/fleet/unreachable_jobs.rs: -------------------------------------------------------------------------------- 1 | use crate::format::problem::*; 2 | use crate::format::solution::*; 3 | use crate::helpers::*; 4 | 5 | #[test] 6 | fn can_use_vehicle_with_open_end() { 7 | let problem = Problem { 8 | plan: Plan { jobs: vec![create_delivery_job("job1", (1., 0.))], ..create_empty_plan() }, 9 | fleet: create_default_fleet(), 10 | ..create_empty_problem() 11 | }; 12 | let matrix = Matrix { 13 | profile: Some("car".to_owned()), 14 | timestamp: None, 15 | travel_times: vec![0, 1, 1, 0], 16 | distances: vec![0, 1, 1, 0], 17 | error_codes: Some(vec![0, 1, 1, 1]), 18 | }; 19 | 20 | let solution = solve_with_metaheuristic(problem, Some(vec![matrix])); 21 | 22 | assert_eq!( 23 | solution, 24 | SolutionBuilder::default() 25 | .unassigned(Some(vec![UnassignedJob { 26 | job_id: "job1".to_string(), 27 | reasons: vec![UnassignedJobReason { 28 | code: "REACHABLE_CONSTRAINT".to_string(), 29 | description: "location unreachable".to_string(), 30 | details: None, 31 | }] 32 | }])) 33 | .build() 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/format/mod.rs: -------------------------------------------------------------------------------- 1 | mod location_custom; 2 | mod location_index; 3 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/group/mod.rs: -------------------------------------------------------------------------------- 1 | mod basic_group; 2 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/limits/mod.rs: -------------------------------------------------------------------------------- 1 | mod max_distance; 2 | mod max_duration; 3 | mod tour_size; 4 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains feature tests: minimalistic tests which check features in isolation 2 | //! and their combination. 3 | 4 | mod breaks; 5 | mod capacity; 6 | mod clustering; 7 | mod compatibility; 8 | mod fleet; 9 | mod format; 10 | mod group; 11 | mod limits; 12 | mod multjob; 13 | mod pickdev; 14 | mod priorities; 15 | mod recharge; 16 | mod relations; 17 | mod reload; 18 | mod skills; 19 | mod timing; 20 | mod tour_shape; 21 | mod unassigned; 22 | mod work_balance; 23 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/multjob/limited_capacity.rs: -------------------------------------------------------------------------------- 1 | use crate::format::problem::*; 2 | use crate::format::solution::*; 3 | use crate::helpers::*; 4 | 5 | #[test] 6 | fn can_handle_limited_capacity() { 7 | let problem = Problem { 8 | plan: Plan { 9 | jobs: vec![ 10 | create_multi_job( 11 | "multi_1", 12 | vec![((1., 0.), 1., vec![1]), ((2., 0.), 1., vec![1])], 13 | vec![((10., 0.), 1., vec![2])], 14 | ), 15 | create_multi_job( 16 | "multi_2", 17 | vec![((3., 0.), 1., vec![1]), ((4., 0.), 1., vec![1])], 18 | vec![((11., 0.), 1., vec![2])], 19 | ), 20 | ], 21 | ..create_empty_plan() 22 | }, 23 | fleet: Fleet { vehicles: vec![create_vehicle_with_capacity("my_vehicle", vec![2])], ..create_default_fleet() }, 24 | ..create_empty_problem() 25 | }; 26 | let matrix = create_matrix_from_problem(&problem); 27 | 28 | let solution = solve_with_metaheuristic(problem, Some(vec![matrix])); 29 | 30 | assert_eq!( 31 | solution.statistic, 32 | Statistic { 33 | cost: 88., 34 | distance: 36, 35 | duration: 42, 36 | times: Timing { driving: 36, serving: 6, ..Timing::default() }, 37 | } 38 | ); 39 | assert!(solution.unassigned.is_none()); 40 | } 41 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/multjob/mod.rs: -------------------------------------------------------------------------------- 1 | mod basic_multi_job; 2 | mod basic_replacement; 3 | mod basic_service; 4 | mod limited_capacity; 5 | mod single_type_places; 6 | mod unassigned_multi_job; 7 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/multjob/unassigned_multi_job.rs: -------------------------------------------------------------------------------- 1 | use crate::format::problem::*; 2 | use crate::format::solution::*; 3 | use crate::helpers::*; 4 | 5 | #[test] 6 | fn can_unassign_multi_job_due_to_capacity() { 7 | let problem = Problem { 8 | plan: Plan { 9 | jobs: vec![create_multi_job( 10 | "multi", 11 | vec![((2., 0.), 1., vec![2]), ((8., 0.), 1., vec![1])], 12 | vec![((6., 0.), 1., vec![3])], 13 | )], 14 | ..create_empty_plan() 15 | }, 16 | fleet: Fleet { vehicles: vec![create_vehicle_with_capacity("my_vehicle", vec![2])], ..create_default_fleet() }, 17 | ..create_empty_problem() 18 | }; 19 | let matrix = create_matrix_from_problem(&problem); 20 | 21 | let solution = solve_with_metaheuristic(problem, Some(vec![matrix])); 22 | 23 | assert_eq!( 24 | solution, 25 | SolutionBuilder::default() 26 | .unassigned(Some(vec![UnassignedJob { 27 | job_id: "multi".to_string(), 28 | reasons: vec![UnassignedJobReason { 29 | code: "CAPACITY_CONSTRAINT".to_string(), 30 | description: "does not fit into any vehicle due to capacity".to_string(), 31 | details: None, 32 | }] 33 | }])) 34 | .build() 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/pickdev/mod.rs: -------------------------------------------------------------------------------- 1 | mod basic_pick_dev; 2 | mod mixed_pick_dev_simple_jobs; 3 | mod relation_pick_dev; 4 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/priorities/mod.rs: -------------------------------------------------------------------------------- 1 | mod basic_order; 2 | mod basic_value; 3 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/recharge/mod.rs: -------------------------------------------------------------------------------- 1 | mod basic_recharge; 2 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/relations/any_basic.rs: -------------------------------------------------------------------------------- 1 | use crate::format::problem::*; 2 | use crate::helpers::*; 3 | use std::panic::catch_unwind; 4 | 5 | #[test] 6 | fn can_skip_constraints_check() { 7 | let problem = Problem { 8 | plan: Plan { 9 | jobs: vec![create_delivery_job("job1", (1., 0.)), create_delivery_job("job2", (2., 0.))], 10 | relations: Some(vec![Relation { 11 | type_field: RelationType::Any, 12 | jobs: to_strings(vec!["departure", "job1", "job2"]), 13 | vehicle_id: "my_vehicle_1".to_string(), 14 | shift_index: None, 15 | }]), 16 | ..create_empty_plan() 17 | }, 18 | fleet: Fleet { 19 | vehicles: vec![VehicleType { capacity: vec![1], ..create_default_vehicle_type() }], 20 | ..create_default_fleet() 21 | }, 22 | ..create_empty_problem() 23 | }; 24 | let matrix = create_matrix_from_problem(&problem); 25 | 26 | let result = catch_unwind(|| solve_with_metaheuristic(problem, Some(vec![matrix]))) 27 | .map_err(|err| err.downcast_ref::().cloned()); 28 | 29 | match result { 30 | Err(Some(err)) => { 31 | assert!(err.starts_with("check failed: 'load exceeds capacity in tour 'my_vehicle_1'")); 32 | } 33 | Err(None) => unreachable!("unknown panic message type"), 34 | Ok(_) => unreachable!("unexpected load or missing checker rule"), 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/relations/mod.rs: -------------------------------------------------------------------------------- 1 | mod any_basic; 2 | mod any_with_new_jobs; 3 | mod mixed_strict_any; 4 | mod mixed_strict_sequence; 5 | mod sequence_with_new_jobs; 6 | mod strict_with_new_jobs; 7 | mod strict_with_old_jobs; 8 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/reload/mod.rs: -------------------------------------------------------------------------------- 1 | mod avoid_reload; 2 | mod basic_reload; 3 | mod diff_reload_places; 4 | mod multi_dim_reload; 5 | mod multi_job_reload; 6 | mod multi_vehicle_reload; 7 | mod picks_devs_reload; 8 | mod shared_reload; 9 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/skills/mod.rs: -------------------------------------------------------------------------------- 1 | mod basic_skill; 2 | mod unassigned_due_to_skills; 3 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/skills/unassigned_due_to_skills.rs: -------------------------------------------------------------------------------- 1 | use crate::format::problem::*; 2 | use crate::format::solution::*; 3 | use crate::helpers::*; 4 | 5 | #[test] 6 | fn can_have_unassigned_due_to_missing_vehicle_skill() { 7 | let problem = Problem { 8 | plan: Plan { 9 | jobs: vec![create_delivery_job_with_skills( 10 | "job1", 11 | (1., 0.), 12 | all_of_skills(vec!["unique_skill".to_string()]), 13 | )], 14 | ..create_empty_plan() 15 | }, 16 | fleet: Fleet { vehicles: vec![create_default_vehicle("vehicle_without_skill")], ..create_default_fleet() }, 17 | ..create_empty_problem() 18 | }; 19 | let matrix = create_matrix_from_problem(&problem); 20 | 21 | let solution = solve_with_metaheuristic(problem, Some(vec![matrix])); 22 | 23 | assert_eq!( 24 | solution, 25 | SolutionBuilder::default() 26 | .unassigned(Some(vec![UnassignedJob { 27 | job_id: "job1".to_string(), 28 | reasons: vec![UnassignedJobReason { 29 | code: "SKILL_CONSTRAINT".to_string(), 30 | description: "cannot serve required skill".to_string(), 31 | details: None 32 | }] 33 | }])) 34 | .build() 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/timing/mod.rs: -------------------------------------------------------------------------------- 1 | mod basic_multiple_times; 2 | mod basic_waiting_time; 3 | mod strict_leads_to_unassigned; 4 | mod strict_split_into_two_tours; 5 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/timing/strict_split_into_two_tours.rs: -------------------------------------------------------------------------------- 1 | use crate::format::problem::*; 2 | use crate::helpers::*; 3 | 4 | #[test] 5 | fn can_split_into_two_tours_because_of_strict_times() { 6 | let problem = Problem { 7 | plan: Plan { 8 | jobs: vec![ 9 | create_delivery_job_with_times("job1", (10., 0.), vec![(70, 80)], 10.), 10 | create_delivery_job_with_times("job2", (20., 0.), vec![(50, 60)], 10.), 11 | create_delivery_job_with_times("job3", (30., 0.), vec![(0, 40), (100, 120)], 10.), 12 | create_delivery_job_with_times("job4", (40., 0.), vec![(0, 40)], 10.), 13 | create_delivery_job_with_times("job5", (50., 0.), vec![(50, 60)], 10.), 14 | ], 15 | ..create_empty_plan() 16 | }, 17 | fleet: Fleet { 18 | vehicles: vec![VehicleType { 19 | vehicle_ids: vec!["my_vehicle_1".to_string(), "my_vehicle_2".to_string()], 20 | ..create_default_vehicle_type() 21 | }], 22 | ..create_default_fleet() 23 | }, 24 | objectives: create_min_jobs_cost_objective(), 25 | ..create_empty_problem() 26 | }; 27 | let matrix = create_matrix_from_problem(&problem); 28 | 29 | let solution = solve_with_metaheuristic(problem, Some(vec![matrix])); 30 | 31 | assert!(solution.unassigned.is_none()); 32 | assert_eq!(solution.tours.len(), 2); 33 | } 34 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/tour_shape/mod.rs: -------------------------------------------------------------------------------- 1 | mod basic_tour_compactness; 2 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/unassigned/mod.rs: -------------------------------------------------------------------------------- 1 | mod multi_reasons; 2 | mod single_reason; 3 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/unassigned/multi_reasons.rs: -------------------------------------------------------------------------------- 1 | use crate::format::problem::*; 2 | use crate::format::solution::*; 3 | use crate::helpers::*; 4 | 5 | #[test] 6 | fn can_have_multiple_unassigned_reasons() { 7 | let problem = Problem { 8 | plan: Plan { 9 | jobs: vec![ 10 | create_delivery_job_with_demand("job1", (1., 0.), vec![9]), 11 | create_delivery_job_with_demand("job2", (1., 0.), vec![9]), 12 | create_delivery_job_with_skills("job3", (1., 0.), all_of_skills(vec!["unique_skill".to_string()])), 13 | ], 14 | ..create_empty_plan() 15 | }, 16 | fleet: Fleet { 17 | vehicles: vec![create_default_vehicle("vehicle1"), create_default_vehicle("vehicle2")], 18 | ..create_default_fleet() 19 | }, 20 | ..create_empty_problem() 21 | }; 22 | let matrix = create_matrix_from_problem(&problem); 23 | 24 | let solution = solve_with_metaheuristic(problem, Some(vec![matrix])); 25 | 26 | assert_eq!(solution.tours.len(), 2); 27 | assert!(solution.unassigned.is_some()); 28 | assert_eq!( 29 | solution.unassigned, 30 | Some(vec![UnassignedJob { 31 | job_id: "job3".to_string(), 32 | reasons: vec![UnassignedJobReason { 33 | code: "SKILL_CONSTRAINT".to_string(), 34 | description: "cannot serve required skill".to_string(), 35 | details: Some(vec![ 36 | UnassignedJobDetail { vehicle_id: "vehicle1_1".to_string(), shift_index: 0 }, 37 | UnassignedJobDetail { vehicle_id: "vehicle2_1".to_string(), shift_index: 0 } 38 | ]) 39 | }] 40 | }]) 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/unassigned/single_reason.rs: -------------------------------------------------------------------------------- 1 | use crate::format::problem::*; 2 | use crate::format::solution::*; 3 | use crate::helpers::*; 4 | 5 | #[test] 6 | fn can_have_empty_detail_in_empty_solution() { 7 | let problem = Problem { 8 | plan: Plan { 9 | jobs: vec![create_delivery_job_with_skills( 10 | "job1", 11 | (1., 0.), 12 | all_of_skills(vec!["unique_skill".to_string()]), 13 | )], 14 | ..create_empty_plan() 15 | }, 16 | fleet: create_default_fleet(), 17 | ..create_empty_problem() 18 | }; 19 | let matrix = create_matrix_from_problem(&problem); 20 | 21 | let solution = solve_with_metaheuristic(problem, Some(vec![matrix])); 22 | 23 | assert_eq!( 24 | solution, 25 | SolutionBuilder::default() 26 | .unassigned(Some(vec![UnassignedJob { 27 | job_id: "job1".to_string(), 28 | reasons: vec![UnassignedJobReason { 29 | code: "SKILL_CONSTRAINT".to_string(), 30 | description: "cannot serve required skill".to_string(), 31 | details: None 32 | }] 33 | }])) 34 | .build() 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/work_balance/balance_max_load.rs: -------------------------------------------------------------------------------- 1 | use crate::format::problem::Objective::*; 2 | use crate::format::problem::*; 3 | use crate::helpers::*; 4 | 5 | #[test] 6 | fn can_balance_max_load() { 7 | let problem = Problem { 8 | plan: Plan { 9 | jobs: vec![ 10 | create_delivery_job("job1", (1., 0.)), 11 | create_delivery_job("job2", (2., 0.)), 12 | create_delivery_job("job3", (3., 0.)), 13 | create_delivery_job("job4", (4., 0.)), 14 | create_delivery_job("job5", (5., 0.)), 15 | create_delivery_job("job6", (6., 0.)), 16 | ], 17 | ..create_empty_plan() 18 | }, 19 | fleet: Fleet { 20 | vehicles: vec![VehicleType { 21 | vehicle_ids: vec!["my_vehicle_1".to_string(), "my_vehicle_2".to_string()], 22 | shifts: vec![create_default_open_vehicle_shift()], 23 | capacity: vec![5], 24 | ..create_default_vehicle_type() 25 | }], 26 | ..create_default_fleet() 27 | }, 28 | objectives: Some(vec![MinimizeUnassigned { breaks: None }, BalanceMaxLoad, MinimizeCost]), 29 | ..create_empty_problem() 30 | }; 31 | let matrix = create_matrix_from_problem(&problem); 32 | 33 | let solution = solve_with_metaheuristic(problem, Some(vec![matrix])); 34 | 35 | assert_eq!(solution.tours.len(), 2); 36 | assert_eq!(solution.tours.first().unwrap().stops.len(), 4); 37 | assert_eq!(solution.tours.last().unwrap().stops.len(), 4); 38 | } 39 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/work_balance/balance_transport.rs: -------------------------------------------------------------------------------- 1 | use crate::format::problem::Objective::*; 2 | use crate::format::problem::*; 3 | use crate::helpers::*; 4 | 5 | #[test] 6 | fn can_balance_duration() { 7 | let problem = Problem { 8 | plan: Plan { 9 | jobs: vec![ 10 | create_delivery_job_with_duration("job1", (1., 0.), 10.), 11 | create_delivery_job_with_duration("job2", (2., 0.), 10.), 12 | create_delivery_job_with_duration("job3", (3., 0.), 10.), 13 | create_delivery_job_with_duration("job4", (4., 0.), 10.), 14 | ], 15 | ..create_empty_plan() 16 | }, 17 | fleet: Fleet { 18 | vehicles: vec![VehicleType { 19 | vehicle_ids: vec!["my_vehicle_1".to_string(), "my_vehicle_2".to_string()], 20 | shifts: vec![create_default_open_vehicle_shift()], 21 | capacity: vec![3], 22 | ..create_default_vehicle_type() 23 | }], 24 | ..create_default_fleet() 25 | }, 26 | objectives: Some(vec![MinimizeUnassigned { breaks: None }, BalanceDuration, MinimizeCost]), 27 | ..create_empty_problem() 28 | }; 29 | let matrix = create_matrix_from_problem(&problem); 30 | 31 | let solution = solve_with_metaheuristic(problem, Some(vec![matrix])); 32 | 33 | assert_eq!(solution.tours.len(), 2); 34 | assert!(solution.tours.first().unwrap().statistic.duration < 30); 35 | assert!(solution.tours.last().unwrap().statistic.duration < 30); 36 | } 37 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/features/work_balance/mod.rs: -------------------------------------------------------------------------------- 1 | mod balance_activities; 2 | mod balance_max_load; 3 | mod balance_transport; 4 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/generator/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module is responsible for the logic which generates problems with specific characteristics. 2 | 3 | extern crate proptest; 4 | extern crate uuid; 5 | 6 | use proptest::prelude::*; 7 | 8 | mod common; 9 | pub use self::common::*; 10 | 11 | mod jobs; 12 | pub use self::jobs::*; 13 | 14 | mod relations; 15 | pub use self::relations::*; 16 | 17 | mod defaults; 18 | pub use self::defaults::*; 19 | 20 | mod vehicles; 21 | pub use self::vehicles::*; 22 | 23 | prop_compose! { 24 | fn from_uints(vec: Vec)(index in 0..vec.len()) -> u64 { 25 | vec[index] 26 | } 27 | } 28 | 29 | prop_compose! { 30 | fn from_usize(vec: Vec)(index in 0..vec.len()) -> usize { 31 | vec[index] 32 | } 33 | } 34 | 35 | prop_compose! { 36 | fn from_strings(vec: Vec)(index in 0..vec.len()) -> String { 37 | vec[index].clone() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/helpers/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | #[path = "../../../vrp-core/tests/helpers/macros.rs"] 3 | #[macro_use] 4 | pub mod macros; 5 | 6 | /// A helper trait to create Location from some type. 7 | pub trait ToLocation { 8 | fn to_loc(self) -> Location; 9 | } 10 | 11 | impl ToLocation for (f64, f64) { 12 | fn to_loc(self) -> Location { 13 | let (lat, lng) = self; 14 | Location::new_coordinate(lat, lng) 15 | } 16 | } 17 | 18 | mod core; 19 | pub use self::core::*; 20 | 21 | mod fixtures; 22 | pub use self::fixtures::*; 23 | 24 | mod solver; 25 | pub use self::solver::*; 26 | 27 | pub mod problem; 28 | pub use self::problem::*; 29 | 30 | pub mod solution; 31 | pub use self::solution::*; 32 | use crate::format::Location; 33 | -------------------------------------------------------------------------------- /vrp-pragmatic/tests/regression/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains tests to catch some regressions using user provided data. 2 | 3 | mod break_test; 4 | mod pd_same_stops; 5 | 6 | const REPEAT_COUNT_MEDIUM: usize = 100; 7 | -------------------------------------------------------------------------------- /vrp-scientific/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vrp-scientific" 3 | description = "An extension logic for solving scientific VRP" 4 | version.workspace = true 5 | authors.workspace = true 6 | license.workspace = true 7 | keywords.workspace = true 8 | categories.workspace = true 9 | readme.workspace = true 10 | homepage.workspace = true 11 | repository.workspace = true 12 | edition.workspace = true 13 | 14 | [dependencies] 15 | vrp-core.workspace = true 16 | paste.workspace = true 17 | 18 | [dev-dependencies] 19 | criterion.workspace = true 20 | 21 | [[bench]] 22 | name = "solomon_goal" 23 | harness = false 24 | -------------------------------------------------------------------------------- /vrp-scientific/README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | The `scientific` crate contains logic to read scientific problems used to benchmark different 4 | ***Vehicle Routing Problem*** related algorithms. 5 | 6 | ## Supported formats 7 | 8 | - **solomon**: see [Solomon benchmark](https://www.sintef.no/projectweb/top/vrptw/solomon-benchmark) 9 | - **lilim**: see [Li&Lim benchmark](https://www.sintef.no/projectweb/top/pdptw/li-lim-benchmark) 10 | 11 | 12 | Please check [the repository](https://github.com/reinterpretcat/vrp) for more details. -------------------------------------------------------------------------------- /vrp-scientific/src/common/text_writer.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | #[path = "../../tests/unit/common/text_writer_test.rs"] 3 | mod text_writer_test; 4 | 5 | use std::io::{BufWriter, Error, Write}; 6 | use vrp_core::models::Solution; 7 | use vrp_core::models::problem::JobIdDimension; 8 | 9 | pub(crate) fn write_text_solution(solution: &Solution, writer: &mut BufWriter) -> Result<(), Error> { 10 | if !solution.unassigned.is_empty() { 11 | return Err(Error::other("cannot write text solution with unassigned jobs.")); 12 | } 13 | 14 | let cost = solution.cost; 15 | 16 | solution.routes.iter().zip(1..).for_each(|(r, i)| { 17 | let customers = r 18 | .tour 19 | .all_activities() 20 | .filter(|a| a.job.is_some()) 21 | .map(|a| a.retrieve_job().unwrap()) 22 | .map(|job| job.dimens().get_job_id().unwrap().clone()) 23 | .collect::>() 24 | .join(" "); 25 | writer.write_all(format!("Route {i}: {customers}\n").as_bytes()).unwrap(); 26 | }); 27 | 28 | writer.write_all(format!("Cost {cost:.2}").as_bytes())?; 29 | 30 | Ok(()) 31 | } 32 | -------------------------------------------------------------------------------- /vrp-scientific/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Scientific crate contains logic to read scientific problems used to benchmark different 2 | //! VRP related algorithms. 3 | //! 4 | //! 5 | //! # Supported formats 6 | //! 7 | //! - **solomon**: see [Solomon benchmark](https://www.sintef.no/projectweb/top/vrptw/solomon-benchmark) 8 | //! - **lilim**: see [Li&Lim benchmark](https://www.sintef.no/projectweb/top/pdptw/li-lim-benchmark) 9 | //! - **tsplib** subset of TSPLIB95 format 10 | 11 | #![warn(missing_docs)] 12 | #![forbid(unsafe_code)] 13 | 14 | #[cfg(test)] 15 | #[path = "../tests/helpers/mod.rs"] 16 | #[macro_use] 17 | pub(crate) mod helpers; 18 | 19 | #[cfg(test)] 20 | #[path = "../tests/integration/known_problems_test.rs"] 21 | mod known_problems_test; 22 | 23 | pub use vrp_core as core; 24 | 25 | pub mod common; 26 | pub mod lilim; 27 | pub mod solomon; 28 | pub mod tsplib; 29 | -------------------------------------------------------------------------------- /vrp-scientific/src/lilim/mod.rs: -------------------------------------------------------------------------------- 1 | //! Contains functionality to read lilim problem and write its solution. 2 | 3 | mod reader; 4 | pub use self::reader::LilimProblem; 5 | 6 | mod writer; 7 | pub use self::writer::LilimSolution; 8 | -------------------------------------------------------------------------------- /vrp-scientific/src/lilim/writer.rs: -------------------------------------------------------------------------------- 1 | use crate::common::write_text_solution; 2 | use std::borrow::Borrow; 3 | use std::io::{BufWriter, Write}; 4 | use vrp_core::prelude::*; 5 | 6 | /// A trait to write lilim solution. 7 | pub trait LilimSolution { 8 | /// Writes lilim solution. 9 | fn write_lilim(&self, writer: &mut BufWriter) -> Result<(), GenericError>; 10 | } 11 | 12 | impl> LilimSolution for B { 13 | fn write_lilim(&self, writer: &mut BufWriter) -> Result<(), GenericError> { 14 | write_text_solution(self.borrow(), writer).map_err(|err| err.to_string())?; 15 | Ok(()) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /vrp-scientific/src/solomon/mod.rs: -------------------------------------------------------------------------------- 1 | //! Contains functionality to read solomon problem and write its solution. 2 | 3 | mod reader; 4 | pub use self::reader::SolomonProblem; 5 | 6 | mod writer; 7 | pub use self::writer::SolomonSolution; 8 | -------------------------------------------------------------------------------- /vrp-scientific/src/solomon/writer.rs: -------------------------------------------------------------------------------- 1 | use crate::common::write_text_solution; 2 | use std::borrow::Borrow; 3 | use std::io::{BufWriter, Write}; 4 | use vrp_core::prelude::*; 5 | 6 | /// A trait to write solomon solution. 7 | pub trait SolomonSolution { 8 | /// Writes solomon solution. 9 | fn write_solomon(&self, writer: &mut BufWriter) -> Result<(), GenericError>; 10 | } 11 | 12 | impl> SolomonSolution for B { 13 | fn write_solomon(&self, writer: &mut BufWriter) -> Result<(), GenericError> { 14 | write_text_solution(self.borrow(), writer).map_err(From::from) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /vrp-scientific/src/tsplib/mod.rs: -------------------------------------------------------------------------------- 1 | //! Contains functionality to read tsplib95 problem and write its solution. 2 | 3 | mod reader; 4 | pub use self::reader::TsplibProblem; 5 | 6 | mod writer; 7 | pub use self::writer::TsplibSolution; 8 | -------------------------------------------------------------------------------- /vrp-scientific/src/tsplib/writer.rs: -------------------------------------------------------------------------------- 1 | use crate::common::write_text_solution; 2 | use std::borrow::Borrow; 3 | use std::io::{BufWriter, Write}; 4 | use vrp_core::prelude::*; 5 | 6 | /// A trait to write tsplib95 solution. 7 | pub trait TsplibSolution { 8 | /// Writes tsplib95 solution. 9 | fn write_tsplib(&self, writer: &mut BufWriter) -> Result<(), GenericError>; 10 | } 11 | 12 | impl> TsplibSolution for B { 13 | fn write_tsplib(&self, writer: &mut BufWriter) -> Result<(), GenericError> { 14 | write_text_solution(self.borrow(), writer).map_err(|err| err.to_string())?; 15 | Ok(()) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /vrp-scientific/tests/helpers/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | #[path = "../../../vrp-core/tests/helpers/macros.rs"] 3 | #[macro_use] 4 | pub mod macros; 5 | 6 | mod analysis; 7 | pub use self::analysis::*; 8 | 9 | mod solomon; 10 | pub use self::solomon::SolomonBuilder; 11 | 12 | use crate::lilim::LilimProblem; 13 | use crate::solomon::SolomonProblem; 14 | use std::fs::File; 15 | use std::io::BufReader; 16 | use vrp_core::models::Problem; 17 | 18 | pub fn get_test_resource(resource_path: &str) -> std::io::Result { 19 | let mut path = std::env::current_dir()?; 20 | path.push("tests"); 21 | path.push(resource_path); 22 | 23 | File::open(path) 24 | } 25 | 26 | pub fn create_c101_25_problem() -> Problem { 27 | BufReader::new(get_test_resource("../../examples/data/scientific/solomon/C101.25.txt").unwrap()) 28 | .read_solomon(false) 29 | .unwrap() 30 | } 31 | 32 | pub fn create_c101_100_problem() -> Problem { 33 | BufReader::new(get_test_resource("../../examples/data/scientific/solomon/C101.100.txt").unwrap()) 34 | .read_solomon(false) 35 | .unwrap() 36 | } 37 | 38 | pub fn create_lc101_problem() -> Problem { 39 | BufReader::new(get_test_resource("../../examples/data/scientific/lilim/LC101.txt").unwrap()) 40 | .read_lilim(false) 41 | .unwrap() 42 | } 43 | -------------------------------------------------------------------------------- /vrp-scientific/tests/helpers/solomon.rs: -------------------------------------------------------------------------------- 1 | pub type Customer = (usize, usize, usize, usize, usize, usize, usize); 2 | 3 | pub struct SolomonBuilder { 4 | title: String, 5 | vehicle: (usize, usize), 6 | customers: Vec, 7 | } 8 | 9 | impl Default for SolomonBuilder { 10 | fn default() -> Self { 11 | Self { title: "My Problem".to_string(), vehicle: (0, 0), customers: vec![] } 12 | } 13 | } 14 | 15 | impl SolomonBuilder { 16 | pub fn set_title(&mut self, title: &str) -> &mut Self { 17 | self.title = title.to_string(); 18 | self 19 | } 20 | 21 | pub fn set_vehicle(&mut self, vehicle: (usize, usize)) -> &mut Self { 22 | self.vehicle = vehicle; 23 | self 24 | } 25 | 26 | pub fn add_customer(&mut self, customer: Customer) -> &mut Self { 27 | self.customers.push(customer); 28 | self 29 | } 30 | 31 | pub fn build(&self) -> String { 32 | let mut data = String::new(); 33 | 34 | data.push_str(format!("{}\n\n", self.title).as_str()); 35 | 36 | data.push_str("VEHICLE\n NUMBER CAPACITY\n"); 37 | data.push_str(format!(" {} {}\n\n", self.vehicle.0, self.vehicle.1).as_str()); 38 | 39 | data.push_str("CUSTOMER\n"); 40 | data.push_str("CUST NO. XCOORD. YCOORD. DEMAND READY TIME DUE DATE SERVICE TIME\n\n"); 41 | self.customers.iter().for_each(|c| { 42 | data.push_str(format!("{} {} {} {} {} {} {} \n", c.0, c.1, c.2, c.3, c.4, c.5, c.6).as_str()); 43 | }); 44 | 45 | data 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /vrp-scientific/tests/unit/common/init_solution_reader_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::helpers::{create_c101_100_problem, get_test_resource}; 3 | use vrp_core::construction::heuristics::InsertionContext; 4 | use vrp_core::utils::{Environment, Float}; 5 | 6 | #[test] 7 | pub fn can_read_init_solution() { 8 | let environment = Arc::new(Environment::default()); 9 | let problem = Arc::new(create_c101_100_problem()); 10 | let file = get_test_resource("../../examples/data/scientific/solomon/C101.100.best.txt").unwrap(); 11 | 12 | let solution = read_init_solution(BufReader::new(file), problem.clone(), environment.random.clone()) 13 | .expect("cannot read initial solution"); 14 | assert_eq!(solution.routes.len(), 10); 15 | assert!(solution.unassigned.is_empty()); 16 | 17 | let insertion_ctx = InsertionContext::new_from_solution(problem, (solution, None), environment); 18 | assert_eq!(insertion_ctx.get_total_cost().unwrap_or_default().round(), (828.936 as Float).round()); 19 | } 20 | -------------------------------------------------------------------------------- /vrp-scientific/tests/unit/common/routing_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use vrp_core::models::common::Profile; 3 | 4 | fn get_index() -> CoordIndex { 5 | let mut index = CoordIndex::default(); 6 | index.collect((0, 0)); 7 | index.collect((2, 1)); 8 | 9 | index 10 | } 11 | 12 | #[test] 13 | fn can_create_transport_without_rounding() { 14 | let index = get_index(); 15 | let logger: InfoLogger = Arc::new(|_| ()); 16 | 17 | let transport = index.create_transport(false, &logger).unwrap(); 18 | 19 | assert!((transport.distance_approx(&Profile::new(0, None), 0, 1) - 2.23606).abs() < 1E-5); 20 | } 21 | 22 | #[test] 23 | fn can_create_transport_with_rounding() { 24 | let index = get_index(); 25 | let logger: InfoLogger = Arc::new(|_| ()); 26 | 27 | let transport = index.create_transport(true, &logger).unwrap(); 28 | 29 | assert_eq!(transport.distance_approx(&Profile::new(0, None), 0, 1), 2.); 30 | } 31 | -------------------------------------------------------------------------------- /vrp-scientific/tests/unit/common/text_writer_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::helpers::SolomonBuilder; 3 | use crate::solomon::{SolomonProblem, SolomonSolution}; 4 | use std::sync::Arc; 5 | use vrp_core::construction::heuristics::InsertionContext; 6 | use vrp_core::rosomaxa::evolution::TelemetryMode; 7 | use vrp_core::solver::search::{Recreate, RecreateWithCheapest}; 8 | use vrp_core::solver::{ElitismPopulation, RefinementContext}; 9 | use vrp_core::utils::Environment; 10 | 11 | #[test] 12 | fn can_write_solomon_solution() { 13 | let environment = Arc::new(Environment::default()); 14 | let problem = Arc::new( 15 | SolomonBuilder::default() 16 | .set_title("Trivial problem") 17 | .set_vehicle((1, 10)) 18 | .add_customer((0, 0, 0, 0, 0, 1000, 1)) 19 | .add_customer((1, 1, 0, 1, 5, 1000, 5)) 20 | .build() 21 | .read_solomon(false) 22 | .unwrap(), 23 | ); 24 | 25 | let refinement_ctx = RefinementContext::new( 26 | problem.clone(), 27 | Box::new(ElitismPopulation::new(problem.goal.clone(), environment.random.clone(), 1, 1)), 28 | TelemetryMode::None, 29 | environment.clone(), 30 | ); 31 | 32 | let mut writer = BufWriter::new(Vec::new()); 33 | let solution: Solution = RecreateWithCheapest::new(environment.random.clone()) 34 | .run(&refinement_ctx, InsertionContext::new(problem, environment)) 35 | .into(); 36 | solution.write_solomon(&mut writer).unwrap(); 37 | let result = String::from_utf8(writer.into_inner().unwrap()).unwrap(); 38 | 39 | assert_eq!(result, "Route 1: 1\nCost 2.00"); 40 | } 41 | -------------------------------------------------------------------------------- /vrp-scientific/tests/unit/lilim/reader_test.rs: -------------------------------------------------------------------------------- 1 | use crate::helpers::{create_lc101_problem, get_job_ids, get_vehicle_capacity}; 2 | 3 | #[test] 4 | fn can_read_lilim_format_from_test_file() { 5 | let problem = create_lc101_problem(); 6 | 7 | assert_eq!(get_job_ids(&problem), (0..53).map(|i| i.to_string()).collect::>()); 8 | assert_eq!(problem.fleet.drivers.len(), 1); 9 | assert_eq!(problem.fleet.vehicles.len(), 25); 10 | assert_eq!(get_vehicle_capacity(&problem), 200); 11 | } 12 | -------------------------------------------------------------------------------- /vrp-scientific/tests/unit/solomon/reader_test.rs: -------------------------------------------------------------------------------- 1 | use crate::helpers::*; 2 | use crate::solomon::SolomonProblem; 3 | 4 | #[test] 5 | fn can_read_solomon_built_from_builder() { 6 | let problem = SolomonBuilder::default() 7 | .set_title("Three customers") 8 | .set_vehicle((2, 10)) 9 | .add_customer((0, 0, 0, 0, 0, 1000, 1)) 10 | .add_customer((1, 1, 0, 1, 5, 1000, 5)) 11 | .add_customer((2, 3, 0, 2, 0, 1002, 11)) 12 | .add_customer((3, 7, 0, 1, 0, 1000, 12)) 13 | .build() 14 | .read_solomon(false) 15 | .unwrap(); 16 | 17 | assert_eq!(get_job_ids(&problem), vec!["1", "2", "3"]); 18 | assert_eq!(get_job_demands(&problem), vec![1, 2, 1]); 19 | assert_eq!(get_vehicle_capacity(&problem), 10); 20 | assert_eq!(get_job_time_windows(&problem), vec![(5., 1000.), (0., 1002.), (0., 1000.)]); 21 | assert_eq!(get_job_durations(&problem), vec![5., 11., 12.]); 22 | 23 | assert_eq!(problem.fleet.drivers.len(), 1); 24 | assert_eq!(problem.fleet.vehicles.len(), 2); 25 | } 26 | 27 | #[test] 28 | fn can_read_solomon_format_from_test_file() { 29 | let problem = create_c101_25_problem(); 30 | 31 | assert_eq!(get_job_ids(&problem), (1..26).map(|i| i.to_string()).collect::>()); 32 | assert_eq!( 33 | get_job_demands(&problem), 34 | vec![10, 30, 10, 10, 10, 20, 20, 20, 10, 10, 10, 20, 30, 10, 40, 40, 20, 20, 10, 10, 20, 20, 10, 10, 40] 35 | ); 36 | assert_eq!(problem.fleet.drivers.len(), 1); 37 | assert_eq!(problem.fleet.vehicles.len(), 25); 38 | assert_eq!(get_vehicle_capacity(&problem), 200); 39 | } 40 | --------------------------------------------------------------------------------