diff --git a/.env b/.env new file mode 100644 index 00000000..589ad321 --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +ORTOOLS_URL="https://github.com/google/or-tools/releases/download/v7.8/or-tools_debian-10_v7.8.7959.tar.gz" +ORTOOLS_VERSION=v7.8 # select from https://github.com/google/or-tools/releases +RUBY_VERSION=2.7 diff --git a/.github/actions/build.sh b/.github/actions/build.sh index a51a4c67..329d2e04 100755 --- a/.github/actions/build.sh +++ b/.github/actions/build.sh @@ -31,8 +31,8 @@ case $ORTOOLS_VERSION in esac echo "Download asset at ${ORTOOLS_URL}" -docker build --build-arg ORTOOLS_URL=${ORTOOLS_URL} -f ./Dockerfile -t "${IMAGE_NAME}" . +docker build --build-arg RUBY_VERSION="2.7" --build-arg ORTOOLS_URL=${ORTOOLS_URL} -f ./Dockerfile -t "${IMAGE_NAME}" . docker run -d --name optimizer -t "${IMAGE_NAME}" docker exec -i optimizer bash -c "LD_LIBRARY_PATH=/srv/or-tools/lib/ /srv/optimizer-ortools/tsp_simple -time_limit_in_ms 500 -intermediate_solutions -instance_file '/srv/optimizer-ortools/data/test_ortools_single_route_with_route_order' -solution_file '/tmp/optimize-or-tools-output'" -echo "::set-output name=image_name::${IMAGE_NAME}" +echo "name=image_name::${IMAGE_NAME}" >> $GITHUB_OUTPUT diff --git a/Dockerfile b/Dockerfile index 6a793a0d..7d74e2a8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,7 @@ -# 1.0.13 is the latest version containing bundler 2 required for optimizer-api -FROM phusion/passenger-ruby25:1.0.13 +ARG RUBY_VERSION +ARG ARCHITECTURE + +FROM ${ARCHITECTURE}ruby:${RUBY_VERSION} ARG ORTOOLS_URL=${ORTOOLS_URL} @@ -7,19 +9,6 @@ LABEL maintainer="Mapotempo " WORKDIR /srv/ -# Trick to install passenger-docker on Ruby 2.5. Othwerwise `apt-get update` fails with a -# certificate error. See following links for explanantion: -# https://issueexplorer.com/issue/phusion/passenger-docker/325 -# and -# https://issueexplorer.com/issue/phusion/passenger-docker/322 -# Basically, DST Root CA X3 certificates are expired on Setember 2021 and apt-get cannot validate -# with the old certificates and the certification correction is only done for Ruby 2.6+ on the -# passenger-docker repo because Ruby 2.5 is EOL. -RUN mv /etc/apt/sources.list.d /etc/apt/sources.list.d.bak -RUN apt update && apt install -y ca-certificates -RUN mv /etc/apt/sources.list.d.bak /etc/apt/sources.list.d -# The above trick can be removed after Ruby version is increased. - RUN apt-get update > /dev/null && \ apt-get -y install git wget pkg-config build-essential cmake autoconf libtool zlib1g-dev lsb-release > /dev/null @@ -30,4 +19,4 @@ RUN wget -qO- $ORTOOLS_URL | tar xz --strip-components=1 -C /srv/or-tools ADD . /srv/optimizer-ortools WORKDIR /srv/optimizer-ortools -RUN make -j3 tsp_simple +RUN make tsp_simple diff --git a/Makefile b/Makefile index 7957b319..135f5b7d 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,30 @@ OR_TOOLS_TOP=../or-tools -TUTORIAL=resources +TUTORIAL=./resources -CFLAGS := -std=c++14 -I $(OR_TOOLS_TOP)/include +# -isystem prevents most of the warnings rooted in or-tools library appearing in our compilation +CFLAGS := -std=c++14 -isystem$(OR_TOOLS_TOP)/include # During development uncomment the next line to have debug checks and other verifications # DEVELOPMENT = true ifeq ($(DEVELOPMENT), true) - # -isystem prevents warnings rooted in or-tools library appearing in our compilation - CFLAGS := $(CFLAGS) -O0 -DDEBUG -ggdb3 -fsanitize=address -fkeep-inline-functions -fno-inline-small-functions -Wall -Wextra -Wshadow -Wunreachable-code -Winit-self -Wmissing-include-dirs -Wswitch-enum -Wfloat-equal -Wundef -isystem$(OR_TOOLS_TOP)/. -isystem$(OR_TOOLS_TOP)/include - CXX := LSAN_OPTION=verbosity=1:log_threads=1 $(CXX) + CFLAGS := $(CFLAGS) -O0 -DDEBUG -ggdb3 -fsanitize=address -fkeep-inline-functions -fno-inline-small-functions + # CXX := ../callcatcher/build/scripts-3.6/callcatcher $(CXX) # comment out to check uncalled functions + CXX := LSAN_OPTION=verbosity=1:log_threads=1 $(CXX) # adress sanitizer works only if the executable launched without gdb else CFLAGS := $(CFLAGS) -O3 -DNDEBUG endif +# Activate warnings +CFLAGS := $(CFLAGS) -Wall -Wextra -Wshadow -Wmissing-include-dirs -Wswitch-enum -Wfloat-equal -Wundef + +# The following is to supress a warning due to a protobuf that is fixed at v3.14. +# It can be removed when or-tools is upgraded to v8.1+ (where the protobuf dependency is upgraded to v3.14). +PROTOBUF_VERSION := $(shell $(OR_TOOLS_TOP)/bin/protoc --version | cut -d" " -f2) +ifeq ($(shell dpkg --compare-versions $(PROTOBUF_VERSION) 'lt' '3.14' && echo true), true) + CFLAGS := $(CFLAGS) -Wno-array-bounds +endif + .PHONY: all local_clean all: $(EXE) @@ -34,7 +45,7 @@ tsp_simple.o: tsp_simple.cc ortools_vrp.pb.h \ tsptw_data_dt.h \ limits.h \ values.h - $(CXX) $(CFLAGS) -I $(TUTORIAL) -c ./tsp_simple.cc -o tsp_simple.o + $(CXX) $(CFLAGS) -isystem$(TUTORIAL) -c ./tsp_simple.cc -o tsp_simple.o tsp_simple: $(ROUTING_DEPS) tsp_simple.o ortools_vrp.pb.o ortools_result.pb.o $(OR_TOOLS_TOP)/lib/libortools.so $(CXX) $(CFLAGS) -fwhole-program tsp_simple.o ortools_vrp.pb.o ortools_result.pb.o $(OR_TOOLS_LD_FLAGS) \ diff --git a/data/test_ortools_single_route_with_route_order b/data/test_ortools_single_route_with_route_order index 2d7596c0..6a027c90 100644 Binary files a/data/test_ortools_single_route_with_route_order and b/data/test_ortools_single_route_with_route_order differ diff --git a/data/test_with_cluster b/data/test_with_cluster new file mode 100644 index 00000000..792ec4a4 Binary files /dev/null and b/data/test_with_cluster differ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..d137c252 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,23 @@ +version: '3.7' +x-app-args: &app-args + ARCHITECTURE: ${ARCHITECTURE} + ORTOOLS_URL: ${ORTOOLS_URL} + ORTOOLS_VERSION: ${ORTOOLS_VERSION} + RUBY_VERSION: ${RUBY_VERSION} + +x-app: &default-app + volumes: + - ./:/srv/app/ + env_file: + - ./.env + +services: + main: + <<: *default-app + build: + args: + <<: *app-args + context: . + dockerfile: Dockerfile + image: dev.example.com/mapotempo-ce/optimizer-ortools + tty: true diff --git a/limits.h b/limits.h index fa1fe66d..9bdf940b 100644 --- a/limits.h +++ b/limits.h @@ -90,8 +90,7 @@ class NoImprovementLimit : public SearchLimit { best_result_ = kint64min; } - DCHECK_NOTNULL(objective_var); - prototype_->AddObjective(objective_var); + prototype_->AddObjective(DCHECK_NOTNULL(objective_var)); } virtual void Init() { @@ -145,7 +144,7 @@ class NoImprovementLimit : public SearchLimit { virtual void Copy(const SearchLimit* const limit) { const NoImprovementLimit* const copy_limit = - reinterpret_cast(limit); + reinterpret_cast(limit); best_result_ = copy_limit->best_result_; solution_nbr_tolerance_ = copy_limit->solution_nbr_tolerance_; @@ -206,8 +205,8 @@ namespace { class LoggerMonitor : public SearchMonitor { public: LoggerMonitor(const TSPTWDataDT& data, RoutingModel* routing, - RoutingIndexManager* manager, int64 min_start, int64 size_matrix, - bool debug, bool intermediate, ortools_result::Result* result, + RoutingIndexManager* manager, int64 size_matrix, bool debug, + bool intermediate, ortools_result::Result* result, std::vector> stored_rests, std::string filename, const bool minimize = true) : SearchMonitor(routing->solver()) @@ -216,7 +215,6 @@ class LoggerMonitor : public SearchMonitor { , manager_(manager) , solver_(routing->solver()) , start_time_(absl::GetCurrentTimeNanos()) - , min_start_(min_start) , size_matrix_(size_matrix) , minimize_(minimize) , limit_reached_(false) @@ -320,6 +318,7 @@ class LoggerMonitor : public SearchMonitor { int64 lateness_cost = 0; int64 overload_cost = 0; bool vehicle_used = false; + const int64 earliest_start = data_.EarliestStart(); for (int64 index = routing_->Start(route_nbr); !routing_->IsEnd(index); index = routing_->NextVar(index)->Value()) { for (std::vector::iterator it = rests.begin(); @@ -339,7 +338,7 @@ class LoggerMonitor : public SearchMonitor { ortools_result::Activity* rest = route->add_activities(); rest->set_type("break"); rest->set_id(parsed_name[1]); - rest->set_start_time(rest_start_time); + rest->set_start_time(rest_start_time + earliest_start); it = rests.erase(it); } else { ++it; @@ -351,7 +350,7 @@ class LoggerMonitor : public SearchMonitor { activity->set_index(data_.ProblemIndex(nodeIndex)); const int64 start_time = routing_->GetMutableDimension(kTime)->CumulVar(index)->Min(); - activity->set_start_time(start_time); + activity->set_start_time(start_time + earliest_start); const int64 upper_bound = routing_->GetMutableDimension(kTime)->GetCumulVarSoftUpperBound(index); const int64 lateness = std::max(start_time - upper_bound, 0); @@ -408,7 +407,7 @@ class LoggerMonitor : public SearchMonitor { const int64 start_time = routing_->GetMutableDimension(kTime)->CumulVar(end_index)->Min(); - end_activity->set_start_time(start_time); + end_activity->set_start_time(start_time + earliest_start); const int64 upper_bound = routing_->GetMutableDimension(kTime)->GetCumulVarSoftUpperBound(end_index); const int64 lateness = std::max(start_time - upper_bound, 0); @@ -560,7 +559,6 @@ class LoggerMonitor : public SearchMonitor { } if (debug_ && new_best) { - std::cout << "min start : " << min_start_ << std::endl; for (RoutingIndexManager::NodeIndex i(0); i < data_.SizeMatrix() - 1; ++i) { const int64 index = manager_->NodeToIndex(i); const IntVar* cumul_var = routing_->GetMutableDimension(kTime)->CumulVar(index); @@ -572,8 +570,8 @@ class LoggerMonitor : public SearchMonitor { slack_var->Bound()) { std::cout << "Node " << i << " index " << index << " [" << vehicle_var->Value() << "] |"; - std::cout << (cumul_var->Value() - min_start_) << " + " << transit_var->Value() - << " -> " << slack_var->Value() << std::endl; + std::cout << (cumul_var->Value()) << " + " << transit_var->Value() << " -> " + << slack_var->Value() << std::endl; } } std::cout << "-----------" << std::endl; @@ -597,8 +595,7 @@ class LoggerMonitor : public SearchMonitor { } virtual void Copy(const SearchLimit* const limit) { - const LoggerMonitor* const copy_limit = - reinterpret_cast(limit); + const LoggerMonitor* const copy_limit = reinterpret_cast(limit); best_result_ = copy_limit->best_result_; cleaned_cost_ = copy_limit->cleaned_cost_; @@ -615,9 +612,9 @@ class LoggerMonitor : public SearchMonitor { // Allocates a clone of the limit virtual SearchMonitor* MakeClone() const { // we don't to copy the variables - return solver_->RevAlloc( - new LoggerMonitor(data_, routing_, manager_, min_start_, size_matrix_, debug_, - intermediate_, result_, stored_rests_, filename_, minimize_)); + return solver_->RevAlloc(new LoggerMonitor(data_, routing_, manager_, size_matrix_, + debug_, intermediate_, result_, + stored_rests_, filename_, minimize_)); } virtual std::string DebugString() const { @@ -644,7 +641,6 @@ class LoggerMonitor : public SearchMonitor { int64 best_result_; double cleaned_cost_; double start_time_; - int64 min_start_; int64 size_matrix_; bool minimize_; bool limit_reached_; @@ -661,14 +657,14 @@ class LoggerMonitor : public SearchMonitor { } // namespace LoggerMonitor* MakeLoggerMonitor(const TSPTWDataDT& data, RoutingModel* routing, - RoutingIndexManager* manager, int64 min_start, - int64 size_matrix, bool debug, bool intermediate, + RoutingIndexManager* manager, int64 size_matrix, + bool debug, bool intermediate, ortools_result::Result* result, std::vector> stored_rests, std::string filename, const bool minimize = true) { return routing->solver()->RevAlloc( - new LoggerMonitor(data, routing, manager, min_start, size_matrix, debug, - intermediate, result, stored_rests, filename, minimize)); + new LoggerMonitor(data, routing, manager, size_matrix, debug, intermediate, result, + stored_rests, filename, minimize)); } } // namespace operations_research diff --git a/ortools_vrp.proto b/ortools_vrp.proto index 1432f53c..576f6e2e 100644 --- a/ortools_vrp.proto +++ b/ortools_vrp.proto @@ -4,6 +4,7 @@ package ortools_vrp; option optimize_for = SPEED; message Matrix { + uint32 size = 1; repeated float time = 2 [ packed = true ]; repeated float distance = 3 [ packed = true ]; repeated float value = 4 [ packed = true ]; @@ -30,6 +31,8 @@ message Service { float exclusion_cost = 13; repeated bool refill_quantities = 14; uint32 problem_index = 15; + string point_id = 16; + uint32 alternative_index = 17; } message Rest { @@ -75,6 +78,7 @@ message Vehicle { uint32 additional_setup = 26; bool free_approach = 27; bool free_return = 28; + string start_point_id = 29; } message Relation { diff --git a/ortools_vrp_pb.rb b/ortools_vrp_pb.rb index ad69e8e8..9dc83a64 100644 --- a/ortools_vrp_pb.rb +++ b/ortools_vrp_pb.rb @@ -6,6 +6,7 @@ Google::Protobuf::DescriptorPool.generated_pool.build do add_file("ortools_vrp.proto", :syntax => :proto3) do add_message "ortools_vrp.Matrix" do + optional :size, :uint32, 1 repeated :time, :float, 2 repeated :distance, :float, 3 repeated :value, :float, 4 @@ -30,6 +31,8 @@ optional :exclusion_cost, :float, 13 repeated :refill_quantities, :bool, 14 optional :problem_index, :uint32, 15 + optional :point_id, :string, 16 + optional :alternative_index, :uint32, 17 end add_message "ortools_vrp.Rest" do optional :time_window, :message, 1, "ortools_vrp.TimeWindow" @@ -72,6 +75,7 @@ optional :additional_setup, :uint32, 26 optional :free_approach, :bool, 27 optional :free_return, :bool, 28 + optional :start_point_id, :string, 29 end add_message "ortools_vrp.Relation" do optional :type, :string, 1 diff --git a/tsp_simple.cc b/tsp_simple.cc index 2316d54f..89b4a701 100644 --- a/tsp_simple.cc +++ b/tsp_simple.cc @@ -80,7 +80,9 @@ double GetUpperBoundCostForDimension(const RoutingModel& routing, void MissionsBuilder(const TSPTWDataDT& data, RoutingModel& routing, RoutingValues& routing_values, RoutingIndexManager& manager, - Assignment* assignment, const int64 size, const int64 min_start) { + Assignment* assignment, const int64 size, + const bool free_approach_return) { + Solver* solver = routing.solver(); const int size_vehicles = data.Vehicles().size(); // const int size_matrix = data.SizeMatrix(); const int size_problem = data.SizeProblem(); @@ -101,6 +103,10 @@ void MissionsBuilder(const TSPTWDataDT& data, RoutingModel& routing, int64 disjunction_cost = !overflow_danger && !CheckOverflow(data_verif, size) ? data_verif : std::pow(2, 52); + std::vector all_vehicle_indices; + for (int v = 0; v < size_vehicles; ++v) + all_vehicle_indices.push_back(v); + for (int activity = 0; activity <= size_problem; ++activity) { std::vector* vect = new std::vector(); @@ -116,16 +122,25 @@ void MissionsBuilder(const TSPTWDataDT& data, RoutingModel& routing, const std::vector& maximum_lateness = data.MaximumLateness(i); const int64 initial_value = routing_values.NodeValues(i).initial_time_value; - IntVar* cumul_var = routing.GetMutableDimension(kTime)->CumulVar(index); + // Timewindows should apply both on kTime and kFakeTime Dimensions.kTime both + // compute the true timings of activities and the time cost kFakeTime allows to + // compute the time cost of the route when there is an free_approach or free_return + // dimension. But it requires true timings to consider correctly waiting times. + // Lateness could only apply on true timings, which means these contraints should + // only be applied on kTime dimension. Dimensions kNoWait and kFakeNoWait doesn't + // need time windows because it expects no waiting times + IntVar* cumul_var = routing.GetMutableDimension(kTime)->CumulVar(index); + if (free_approach_return) + solver->AddConstraint(solver->MakeEquality( + routing.GetMutableDimension(kFakeTime)->CumulVar(index), cumul_var)); + const int64 late_multiplier = data.LateMultiplier(i); - std::vector sticky_vehicle = data.VehicleIndices(i); - std::string service_id = data.ServiceId(i); + std::string service_id = data.ServiceId(i); if (ready.size() > 0 && (ready[0] > -CUSTOM_MAX_INT || due.back() < CUSTOM_MAX_INT)) { if (absl::GetFlag(FLAGS_debug)) { - std::cout << "Node " << i << " index " << index << " [" - << (ready[0] - min_start) << " : " << (due.back() - min_start) - << "]:" << data.ServiceTime(i) << std::endl; + std::cout << "Node " << i << " index " << index << " [" << (ready[0]) << " : " + << (due.back()) << "]:" << data.ServiceTime(i) << std::endl; } if (ready[0] > -CUSTOM_MAX_INT) { cumul_var->SetMin(ready[0]); @@ -163,25 +178,15 @@ void MissionsBuilder(const TSPTWDataDT& data, RoutingModel& routing, } } - if (sticky_vehicle.size() > 0) { - std::vector vehicle_indices; - std::vector vehicle_intersection; - std::vector vehicle_difference; - - for (int v = 0; v < size_vehicles; ++v) - vehicle_indices.push_back(v); - - std::set_intersection(vehicle_indices.begin(), vehicle_indices.end(), - sticky_vehicle.begin(), sticky_vehicle.end(), - std::back_inserter(vehicle_intersection)); - std::set_difference(vehicle_indices.begin(), vehicle_indices.end(), - vehicle_intersection.begin(), vehicle_intersection.end(), - std::back_inserter(vehicle_difference)); - if (vehicle_difference.size() > 0) { - for (int64 remove : vehicle_difference) - routing.VehicleVar(index)->RemoveValue(remove); - } - } + std::vector incompatible_vehicle_indices; + std::vector compatible_vehicles = data.VehicleIndices(i); + + std::set_difference(all_vehicle_indices.begin(), all_vehicle_indices.end(), + compatible_vehicles.begin(), compatible_vehicles.end(), + std::back_inserter(incompatible_vehicle_indices)); + + for (int64 remove : incompatible_vehicle_indices) + routing.VehicleVar(index)->RemoveValue(remove); const std::vector& refill_quantities = data.RefillQuantities(i); for (std::size_t q = 0; q < data.Quantities(i).size(); ++q) { @@ -247,7 +252,10 @@ bool RouteBuilder(const TSPTWDataDT& data, RoutingModel& routing, route_variable_indicies.end()); } } - return routing.RoutesToAssignment(routes, true, false, assignment); + + assignment = routing.ReadAssignmentFromRoutes(routes, true); + + return assignment == nullptr ? false : true; } std::vector> @@ -279,8 +287,8 @@ RestBuilder(const TSPTWDataDT& data, RoutingModel& routing, const int64 horizon) const TSPTWDataDT::Vehicle& vehicle = data.Vehicles(vehicle_index); for (const TSPTWDataDT::Rest& rest : vehicle.Rests()) { IntervalVar* const rest_interval = solver->MakeFixedDurationIntervalVar( - std::max(rest.ready_time, vehicle.time_start), - std::min(rest.due_time, vehicle.time_end - rest.duration), + std::max(rest.ready_time, vehicle.time_start), + std::min(rest.due_time, vehicle.time_end - rest.duration), rest.duration, // Currently only one timewindow false, absl::StrCat("Rest/", rest.rest_id, "/", vehicle_index)); rest_array.push_back(rest_interval); @@ -300,21 +308,13 @@ RestBuilder(const TSPTWDataDT& data, RoutingModel& routing, const int64 horizon) routing.GetMutableDimension(kTime)->SetBreakIntervalsOfVehicle( rest_array, vehicle_index, data.ServiceTimes()); - if (vehicle.max_interval_between_breaks > 0) { - DLOG(INFO) << "\n\nSetting max break distance to " - << vehicle.max_interval_between_breaks << std::endl; - // Put an upperbound on the intervals between breaks that are longer than "dur" - routing.GetMutableDimension(kTime)->SetBreakDistanceDurationOfVehicle( - /*upperbound*/ vehicle.max_interval_between_breaks, /*dur*/ 0, vehicle_index); - } - stored_rests.push_back(rest_array); } return stored_rests; } void RelationBuilder(const TSPTWDataDT& data, RoutingModel& routing, - RoutingIndexManager& manager, bool& has_overall_duration) { + bool& has_overall_duration) { Solver* solver = routing.solver(); // const int size_vehicles = data.Vehicles().size(); @@ -326,10 +326,6 @@ void RelationBuilder(const TSPTWDataDT& data, RoutingModel& routing, return data.DayIndexToVehicleIndex(index); }; - // Solver::IndexEvaluator1 alternative_vehicle_evaluator = [&data](int64 index) { - // return data.VehicleDayAlt(index); - // }; - std::vector next_vars; for (int i = 0; i < data.SizeMissions(); ++i) { next_vars.push_back(routing.NextVar(i)); @@ -348,6 +344,11 @@ void RelationBuilder(const TSPTWDataDT& data, RoutingModel& routing, RoutingModel::DisjunctionIndex current_disjunction_index; std::vector previous_vehicle_index_set; std::vector current_vehicle_index_set; + std::vector previous_active_var_set; + std::vector current_active_var_set; + std::vector current_active_index_set; + std::vector previous_active_next_var_set; + std::vector current_active_next_var_set; std::vector previous_vehicle_var_set; std::vector current_vehicle_var_set; std::vector previous_active_cumul_var_set; @@ -355,81 +356,186 @@ void RelationBuilder(const TSPTWDataDT& data, RoutingModel& routing, switch (relation.type) { case Sequence: - // int64 new_current_index; - previous_index = data.IdIndex(relation.linked_ids[0]); - for (int link_index = 1; link_index < relation.linked_ids.size(); ++link_index) { - previous_indices.push_back(previous_index); + for (int link_index = 0; link_index < relation.linked_ids.size(); ++link_index) { current_index = data.IdIndex(relation.linked_ids[link_index]); + int32 service_index = + data.ProblemIndex(RoutingIndexManager::NodeIndex(current_index)); + alternative_size = data.AlternativeSize(service_index); - pairs.push_back(std::make_pair(previous_index, current_index)); + current_active_var_set.clear(); + current_active_cumul_var_set.clear(); + current_vehicle_var_set.clear(); + current_active_next_var_set.clear(); + current_active_index_set.clear(); + for (int64 alternative_index = current_index; + alternative_index < current_index + alternative_size; ++alternative_index) { + IntVar* const active_node = routing.ActiveVar(alternative_index); + current_vehicle_var_set.push_back( + solver->MakeProd(active_node, routing.VehicleVar(alternative_index)) + ->Var()); - IntVar* const previous_active_var = routing.ActiveVar(previous_index); - IntVar* const active_var = routing.ActiveVar(current_index); - solver->AddConstraint(solver->MakeLessOrEqual(active_var, previous_active_var)); - // routing.AddPickupAndDelivery(previous_index, current_index); + // The next var of the current active alternative + current_active_next_var_set.push_back( + solver->MakeProd(active_node, routing.NextVar(alternative_index))->Var()); - IntVar* const previous_vehicle_var = routing.VehicleVar(previous_index); - IntVar* const vehicle_var = routing.VehicleVar(current_index); - IntExpr* const isConstraintActive = - solver->MakeProd(previous_active_var, active_var)->Var(); - routing.NextVar(current_index)->RemoveValues(previous_indices); + // The index of the current alternative + current_active_index_set.push_back( + solver->MakeProd(active_node, alternative_index)->Var()); - solver->AddConstraint(solver->MakeEquality( - solver->MakeProd(isConstraintActive, previous_vehicle_var), - solver->MakeProd(isConstraintActive, vehicle_var))); - solver->AddConstraint(solver->MakeEquality( - solver->MakeProd(isConstraintActive, routing.NextVar(previous_index)), - solver->MakeProd(isConstraintActive, current_index))); - previous_index = current_index; + current_active_var_set.push_back(active_node); + current_active_cumul_var_set.push_back( + solver + ->MakeProd(active_node, routing.GetMutableDimension(kTime)->CumulVar( + alternative_index)) + ->Var()); + routing.NextVar(alternative_index)->RemoveValues(previous_indices); + previous_indices.push_back(alternative_index); + } + + if (link_index >= 1) { + // The constraint is only active if both of the services have one active + // alternative + IntVar* active_constraint = + solver + ->MakeProd(solver->MakeMax(previous_active_var_set), + solver->MakeMax(current_active_var_set)) + ->Var(); + // The current set may be active only if the predecessor set is active + solver->AddConstraint( + solver->MakeLessOrEqual(solver->MakeMax(current_active_var_set), + solver->MakeMax(previous_active_var_set))); + + // The current active alternative node should belong to the same route than the + // active predecessor + solver->AddConstraint(solver->MakeEquality( + solver->MakeProd(active_constraint, + solver->MakeMax(previous_vehicle_var_set)), + solver->MakeProd(active_constraint, + solver->MakeMax(current_vehicle_var_set)))); + + // The successor of the predecessor should be the current active alternative + // node + solver->AddConstraint(solver->MakeEquality( + solver->MakeProd(active_constraint, + solver->MakeMax(previous_active_next_var_set)), + solver->MakeProd(active_constraint, + solver->MakeMax(current_active_index_set)))); + + // The current active alternative should start after the active predecessor + solver->AddConstraint(solver->MakeLessOrEqual( + solver->MakeProd(active_constraint, + solver->MakeMax(previous_active_cumul_var_set)), + solver->MakeProd(active_constraint, + solver->MakeMax(current_active_cumul_var_set)))); + } + previous_active_var_set = current_active_var_set; + previous_active_next_var_set = current_active_next_var_set; + previous_vehicle_var_set = current_vehicle_var_set; + previous_active_cumul_var_set = current_active_cumul_var_set; } - if (relation.linked_ids.size() > 1) - solver->AddConstraint(solver->MakePathPrecedenceConstraint(next_vars, pairs)); break; case Order: - previous_index = data.IdIndex(relation.linked_ids[0]); - previous_indices.push_back(previous_index); - for (int link_index = 1; link_index < relation.linked_ids.size(); ++link_index) { + for (int link_index = 0; link_index < relation.linked_ids.size(); ++link_index) { current_index = data.IdIndex(relation.linked_ids[link_index]); + int32 service_index = + data.ProblemIndex(RoutingIndexManager::NodeIndex(current_index)); + alternative_size = data.AlternativeSize(service_index); - pairs.push_back(std::make_pair(previous_index, current_index)); - // routing.AddPickupAndDelivery(previous_index, current_index); - IntVar* const previous_active_var = routing.ActiveVar(previous_index); - IntVar* const active_var = routing.ActiveVar(current_index); + current_active_var_set.clear(); + current_vehicle_var_set.clear(); + current_active_cumul_var_set.clear(); - IntVar* const previous_vehicle_var = routing.VehicleVar(previous_index); - IntVar* const vehicle_var = routing.VehicleVar(current_index); - routing.NextVar(current_index)->RemoveValues(previous_indices); + for (int64 alternative_index = current_index; + alternative_index < current_index + alternative_size; ++alternative_index) { + IntVar* active_node = routing.ActiveVar(alternative_index); + current_active_var_set.push_back(active_node); + current_vehicle_var_set.push_back(routing.VehicleVar(alternative_index)); + current_active_cumul_var_set.push_back( + solver + ->MakeProd(active_node, routing.GetMutableDimension(kTime)->CumulVar( + alternative_index)) + ->Var()); + routing.NextVar(alternative_index)->RemoveValues(previous_indices); + previous_indices.push_back(alternative_index); + } - solver->AddConstraint(solver->MakeLessOrEqual(active_var, previous_active_var)); - IntExpr* const isConstraintActive = - solver->MakeProd(previous_active_var, active_var); - solver->AddConstraint(solver->MakeEquality( - solver->MakeProd(isConstraintActive, previous_vehicle_var), - solver->MakeProd(isConstraintActive, vehicle_var))); - previous_indices.push_back(current_index); - previous_index = current_index; + if (link_index >= 1) { + // The constraint is only active if both of the services have one active + // alternative + IntVar* active_constraint = + solver + ->MakeProd(solver->MakeMax(previous_active_var_set), + solver->MakeMax(current_active_var_set)) + ->Var(); + + // The current set may be active only if the predessor set is active + solver->AddConstraint( + solver->MakeLessOrEqual(solver->MakeMax(current_active_var_set), + solver->MakeMax(previous_active_var_set))); + // The active alternatives should belong to the same route + solver->AddConstraint(solver->MakeEquality( + solver->MakeProd(active_constraint, + solver->MakeMax(previous_vehicle_var_set)), + solver->MakeProd(active_constraint, + solver->MakeMax(current_vehicle_var_set)))); + + // The current active alternative should start after the active predecessor + solver->AddConstraint(solver->MakeLessOrEqual( + solver->MakeProd(active_constraint, + solver->MakeMax(previous_active_cumul_var_set)), + solver->MakeProd(active_constraint, + solver->MakeMax(current_active_cumul_var_set)))); + } + previous_active_var_set = current_active_var_set; + previous_vehicle_var_set = current_vehicle_var_set; + previous_active_cumul_var_set = current_active_cumul_var_set; } - if (relation.linked_ids.size() > 1) - solver->AddConstraint(solver->MakePathPrecedenceConstraint(next_vars, pairs)); break; case SameRoute: - previous_index = data.IdIndex(relation.linked_ids[0]); - for (int link_index = 1; link_index < relation.linked_ids.size(); ++link_index) { + for (int link_index = 0; link_index < relation.linked_ids.size(); ++link_index) { current_index = data.IdIndex(relation.linked_ids[link_index]); + int32 service_index = + data.ProblemIndex(RoutingIndexManager::NodeIndex(current_index)); + alternative_size = data.AlternativeSize(service_index); - IntVar* const previous_active_var = routing.ActiveVar(previous_index); - IntVar* const active_var = routing.ActiveVar(current_index); + current_active_var_set.clear(); + current_vehicle_var_set.clear(); + for (int64 alternative_index = current_index; + alternative_index < current_index + alternative_size; ++alternative_index) { + IntVar* const active_node = routing.ActiveVar(alternative_index); + current_active_var_set.push_back(active_node); + // Current vehicle var is active only if one of the previous active var is + // active and if the current alternative is active + current_vehicle_var_set.push_back( + solver + ->MakeProd(solver->MakeProd(solver->MakeMax(previous_active_var_set), + active_node), + routing.VehicleVar(alternative_index)) + ->Var()); + } - IntVar* const previous_vehicle_var = routing.VehicleVar(previous_index); - IntVar* const vehicle_var = routing.VehicleVar(current_index); + if (link_index >= 1) { + // The constraint is only active if both of the services have one active + // alternative + IntVar* active_constraint = + solver + ->MakeProd(solver->MakeMax(previous_active_var_set), + solver->MakeMax(current_active_var_set)) + ->Var(); - solver->AddConstraint(solver->MakeLessOrEqual(active_var, previous_active_var)); - IntVar* const isConstraintActive = - solver->MakeProd(previous_active_var, active_var)->Var(); - solver->AddConstraint(solver->MakeEquality( - solver->MakeProd(previous_vehicle_var, isConstraintActive), - solver->MakeProd(vehicle_var, isConstraintActive))); - previous_index = current_index; + // The current set may be active only if the predessor set is active + solver->AddConstraint( + solver->MakeLessOrEqual(solver->MakeMax(current_active_var_set), + solver->MakeMax(previous_active_var_set))); + // The active alternatives should belong to the same route + solver->AddConstraint(solver->MakeEquality( + solver->MakeProd(active_constraint, + solver->MakeMax(previous_vehicle_var_set)), + solver->MakeProd(active_constraint, + solver->MakeMax(current_vehicle_var_set)))); + } + previous_active_var_set = current_active_var_set; + previous_vehicle_var_set = current_vehicle_var_set; } break; case MinimumDayLapse: @@ -509,18 +615,20 @@ void RelationBuilder(const TSPTWDataDT& data, RoutingModel& routing, IntVar* const active_node = routing.ActiveVar(alternative_index); current_active_cumul_var_set.push_back( solver - ->MakeProd(active_node, - routing.GetMutableDimension(kTime)->CumulVar(current_index)) + ->MakeProd(active_node, routing.GetMutableDimension(kTime)->CumulVar( + alternative_index)) ->Var()); } if (link_index >= 1) { routing.AddPickupAndDeliverySets(previous_disjunction_index, current_disjunction_index); + // The active alternatives should belong to the same route solver->AddConstraint( solver->MakeEquality(solver->MakeMax(previous_vehicle_var_set), solver->MakeMax(current_vehicle_var_set))); + // The current active alternative should start after the active predecessor solver->AddConstraint( solver->MakeLessOrEqual(solver->MakeMax(previous_active_cumul_var_set), solver->MakeMax(current_active_cumul_var_set))); @@ -613,13 +721,22 @@ void RelationBuilder(const TSPTWDataDT& data, RoutingModel& routing, break; case NeverFirst: for (int link_index = 0; link_index < relation.linked_ids.size(); ++link_index) { - current_index = data.IdIndex(relation.linked_ids[link_index]); - - for (std::size_t v = 0; v < data.Vehicles().size(); ++v) { - int64 start_index = routing.Start(v); - // int64 end_index = routing.End(v); - IntVar* const next_var = routing.NextVar(start_index); - next_var->RemoveValue(current_index); + current_index = data.AlternativeActivityIndex(relation.linked_ids[link_index]); + if (current_index >= 0) { + alternative_size = + data.AlternativeActivitySize(relation.linked_ids[link_index]); + } else { + current_index = data.IdIndex(relation.linked_ids[link_index]); + alternative_size = data.AlternativeSize(current_index); + } + for (int64 alternative_index = current_index; + alternative_index < current_index + alternative_size; ++alternative_index) { + for (std::size_t v = 0; v < data.Vehicles().size(); ++v) { + int64 start_index = routing.Start(v); + // int64 end_index = routing.End(v); + IntVar* const next_var = routing.NextVar(start_index); + next_var->RemoveValue(alternative_index); + } } } break; @@ -629,13 +746,23 @@ void RelationBuilder(const TSPTWDataDT& data, RoutingModel& routing, } for (int link_index = 0; link_index < relation.linked_ids.size(); ++link_index) { - current_index = data.IdIndex(relation.linked_ids[link_index]); + current_index = data.AlternativeActivityIndex(relation.linked_ids[link_index]); + if (current_index >= 0) { + alternative_size = + data.AlternativeActivitySize(relation.linked_ids[link_index]); + } else { + current_index = data.IdIndex(relation.linked_ids[link_index]); + alternative_size = data.AlternativeSize(current_index); + } - intermediate_values.push_back(current_index); + for (int64 alternative_index = current_index; + alternative_index < current_index + alternative_size; ++alternative_index) { + intermediate_values.push_back(alternative_index); - std::vector::iterator it = - std::find(values.begin(), values.end(), current_index); - values.erase(it); + std::vector::iterator it = + std::find(values.begin(), values.end(), alternative_index); + values.erase(it); + } } for (int index : values) { @@ -645,20 +772,40 @@ void RelationBuilder(const TSPTWDataDT& data, RoutingModel& routing, break; case NeverLast: for (int link_index = 0; link_index < relation.linked_ids.size(); ++link_index) { - current_index = data.IdIndex(relation.linked_ids[link_index]); + current_index = data.AlternativeActivityIndex(relation.linked_ids[link_index]); + if (current_index >= 0) { + alternative_size = + data.AlternativeActivitySize(relation.linked_ids[link_index]); + } else { + current_index = data.IdIndex(relation.linked_ids[link_index]); + alternative_size = data.AlternativeSize(current_index); + } - IntVar* const next_var = routing.NextVar(current_index); - for (std::size_t v = 0; v < data.Vehicles().size(); ++v) { - int64 end_index = routing.End(v); - next_var->RemoveValue(end_index); + for (int64 alternative_index = current_index; + alternative_index < current_index + alternative_size; ++alternative_index) { + IntVar* const next_var = routing.NextVar(alternative_index); + for (std::size_t v = 0; v < data.Vehicles().size(); ++v) { + int64 end_index = routing.End(v); + next_var->RemoveValue(end_index); + } } } break; case ForceLast: for (int link_index = 0; link_index < relation.linked_ids.size(); ++link_index) { - current_index = data.IdIndex(relation.linked_ids[link_index]); + current_index = data.AlternativeActivityIndex(relation.linked_ids[link_index]); + if (current_index >= 0) { + alternative_size = + data.AlternativeActivitySize(relation.linked_ids[link_index]); + } else { + current_index = data.IdIndex(relation.linked_ids[link_index]); + alternative_size = data.AlternativeSize(current_index); + } - values.push_back(current_index); + for (int64 alternative_index = current_index; + alternative_index < current_index + alternative_size; ++alternative_index) { + values.push_back(alternative_index); + } } intermediate_values.insert(intermediate_values.end(), values.begin(), values.end()); for (std::size_t v = 0; v < data.Vehicles().size(); ++v) { @@ -739,7 +886,7 @@ void RelationBuilder(const TSPTWDataDT& data, RoutingModel& routing, default: std::cout << "ERROR: Relation type (" << relation.type << ") is not implemented" << std::endl; - throw - 1; + throw -1; } } } @@ -985,11 +1132,18 @@ void AddValueDimensions(const TSPTWDataDT& data, RoutingModel& routing, RoutingIndexManager& manager) { std::vector value_evaluators; for (const TSPTWDataDT::Vehicle& vehicle : data.Vehicles()) { - value_evaluators.push_back(routing.RegisterTransitCallback( - [&vehicle, &manager](const int64 i, const int64 j) { - return vehicle.ValuePlusServiceValue(manager.IndexToNode(i), - manager.IndexToNode(j)); - })); + if (vehicle.value_matrix->value().empty()) { + value_evaluators.push_back( + routing.RegisterTransitCallback([&data, &manager](const int64 i, const int64) { + return data.ServiceValue(manager.IndexToNode(i)); + })); + } else { + value_evaluators.push_back(routing.RegisterTransitCallback( + [&vehicle, &data, &manager](const int64 i, const int64 j) { + return vehicle.Value(manager.IndexToNode(i), manager.IndexToNode(j)) + + data.ServiceValue(manager.IndexToNode(i)); + })); + } } routing.AddDimensionWithVehicleTransits(value_evaluators, 0, LLONG_MAX, true, kValue); int v = 0; @@ -1098,11 +1252,9 @@ void AddVehicleDistanceConstraints(const TSPTWDataDT& data, RoutingModel& routin routing.GetDimensionOrDie(kDistance); for (const TSPTWDataDT::Vehicle& vehicle : data.Vehicles()) { if (vehicle.distance > 0) { - const int64 end_index = routing.End(v); // Vehicle maximum distance - IntVar* const dist_end_cumul_var = distance_dimension.CumulVar(end_index); - solver->AddConstraint( - solver->MakeLessOrEqual(dist_end_cumul_var, vehicle.distance)); + solver->AddConstraint(solver->MakeLessOrEqual( + distance_dimension.CumulVar(routing.End(v)), vehicle.distance)); } ++v; } @@ -1199,6 +1351,8 @@ void ParseSolutionIntoResult(const Assignment* const solution, std::vector>& stored_rests) { result->clear_routes(); + const int64_t earliest_start = data.EarliestStart(); + double total_time_order_cost(0.0), total_distance_order_cost(0.0), total_rest_position_cost(0.0); @@ -1228,7 +1382,7 @@ void ParseSolutionIntoResult(const Assignment* const solution, ortools_result::Activity* rest = route->add_activities(); rest->set_type("break"); rest->set_id(parsed_name[1]); - rest->set_start_time(rest_start_time); + rest->set_start_time(rest_start_time + earliest_start); it = rests.erase(it); } else { ++it; @@ -1238,7 +1392,7 @@ void ParseSolutionIntoResult(const Assignment* const solution, ortools_result::Activity* activity = route->add_activities(); RoutingIndexManager::NodeIndex nodeIndex = manager.IndexToNode(index); activity->set_index(data.ProblemIndex(nodeIndex)); - activity->set_start_time(start_time); + activity->set_start_time(start_time + earliest_start); const int64 upper_bound = routing.GetMutableDimension(kTime)->GetCumulVarSoftUpperBound(index); const int64 lateness = std::max(start_time - upper_bound, 0); @@ -1250,7 +1404,8 @@ void ParseSolutionIntoResult(const Assignment* const solution, activity->set_type("start"); DLOG(INFO) << "RouteStartValues:" << route_nbr << "\t start_time: " << start_time << std::endl; - routing_values.RouteStartValues(route_nbr).initial_time_value = start_time; + routing_values.RouteStartValues(route_nbr).initial_time_value = + start_time + earliest_start; } else { vehicle_used = true; activity->set_type("service"); @@ -1258,7 +1413,8 @@ void ParseSolutionIntoResult(const Assignment* const solution, activity->set_alternative(data.AlternativeIndex(nodeIndex)); DLOG(INFO) << "nodeIndex:" << nodeIndex << "\t start_time: " << start_time << std::endl; - routing_values.NodeValues(nodeIndex).initial_time_value = start_time; + routing_values.NodeValues(nodeIndex).initial_time_value = + start_time + earliest_start; } for (std::size_t q = 0; q < data.Quantities(RoutingIndexManager::NodeIndex(0)).size(); ++q) { @@ -1288,7 +1444,7 @@ void ParseSolutionIntoResult(const Assignment* const solution, } rest->set_type("break"); rest->set_id(parsed_name[1]); - rest->set_start_time(rest_start_time); + rest->set_start_time(rest_start_time + earliest_start); } } @@ -1300,7 +1456,7 @@ void ParseSolutionIntoResult(const Assignment* const solution, const int64 start_time = solution->Min(routing.GetMutableDimension(kTime)->CumulVar(end_index)); - end_activity->set_start_time(start_time); + end_activity->set_start_time(start_time + earliest_start); const int64 upper_bound = routing.GetMutableDimension(kTime)->GetCumulVarSoftUpperBound(end_index); const int64 lateness = std::max(start_time - upper_bound, 0); @@ -1366,7 +1522,10 @@ void ParseSolutionIntoResult(const Assignment* const solution, const double time_without_wait_cost = GetSpanCostForVehicleForDimension(routing, solution, route_nbr, kTimeNoWait); - route_costs->set_time_without_wait(time_without_wait_cost); + const double fake_time_without_wait_cost = GetSpanCostForVehicleForDimension( + routing, solution, route_nbr, kFakeTimeNoWait); + route_costs->set_time_without_wait(time_without_wait_cost + + fake_time_without_wait_cost); const double value_cost = GetSpanCostForVehicleForDimension(routing, solution, route_nbr, kValue); @@ -1447,12 +1606,10 @@ const ortools_result::Result* TSPTWSolver(const TSPTWDataDT& data, AddVehicleDistanceConstraints(data, routing); AddVehicleCapacityConstraints(data, routing); - v = 0; - int64 min_start = CUSTOM_MAX_INT; + v = 0; for (const TSPTWDataDT::Vehicle& vehicle : data.Vehicles()) { routing.SetFixedCostOfVehicle(vehicle.cost_fixed, v); - min_start = std::min(min_start, vehicle.time_start); ++v; } @@ -1517,10 +1674,10 @@ const ortools_result::Result* TSPTWSolver(const TSPTWDataDT& data, // Setting visit time windows MissionsBuilder(data, routing, routing_values, manager, assignment, size - 2, - min_start); + free_approach_return); std::vector> stored_rests = RestBuilder(data, routing, horizon); - RelationBuilder(data, routing, manager, has_overall_duration); + RelationBuilder(data, routing, has_overall_duration); RoutingSearchParameters parameters = DefaultRoutingSearchParameters(); CHECK(google::protobuf::TextFormat::MergeFromString( @@ -1560,7 +1717,7 @@ const ortools_result::Result* TSPTWSolver(const TSPTWDataDT& data, const bool build_route = RouteBuilder(data, routing, manager, assignment); LoggerMonitor* const logger = MakeLoggerMonitor( - data, &routing, &manager, min_start, size_matrix, absl::GetFlag(FLAGS_debug), + data, &routing, &manager, size_matrix, absl::GetFlag(FLAGS_debug), absl::GetFlag(FLAGS_intermediate_solutions), result, stored_rests, filename, true); routing.AddSearchMonitor(logger); diff --git a/tsptw_data_dt.h b/tsptw_data_dt.h index 6cc1ebaa..fea7eb95 100644 --- a/tsptw_data_dt.h +++ b/tsptw_data_dt.h @@ -21,6 +21,13 @@ #define CUSTOM_BIGNUM_QUANTITY 1e3 // Needs to stay smaller than CUSTOM_BIGNUM_COST +static bool compare_less_than_custom_max_int(const float& value1, const float& value2) { + if (value2 < CUSTOM_MAX_INT && value1 < value2) + return true; + else + return false; +} + enum RelationType { MinimumDurationLapse = 15, VehicleGroupNumber = 14, @@ -45,75 +52,37 @@ namespace operations_research { class TSPTWDataDT { public: - explicit TSPTWDataDT(const std::string& filename) { LoadInstance(filename); } - - void LoadInstance(const std::string& filename); - - // Helper function - int64& SetDistMatrix(const int i, const int j) { - return distances_matrices_.back().Cost(RoutingIndexManager::NodeIndex(i), - RoutingIndexManager::NodeIndex(j)); - } - - int64& SetTimeMatrix(const int i, const int j) { - return times_matrices_.back().Cost(RoutingIndexManager::NodeIndex(i), - RoutingIndexManager::NodeIndex(j)); - } - - int64& SetValueMatrix(const int i, const int j) { - return values_matrices_.back().Cost(RoutingIndexManager::NodeIndex(i), - RoutingIndexManager::NodeIndex(j)); - } - - int64 BuildTimeMatrix(const ortools_vrp::Matrix& matrix) { - int64 max_time = 0; - const int32 size_matrix = sqrt(matrix.time_size()); - for (int64 i = 0; i < size_matrix; ++i) { - for (int64 j = 0; j < size_matrix; ++j) { - const int64 time = matrix.time(i * size_matrix + j) + 0.5; - if (time < CUSTOM_MAX_INT) - max_time = std::max(max_time, time); - SetTimeMatrix(i, j) = time; - } - // std::cout << std::endl; - } - - return max_time; + explicit TSPTWDataDT(const std::string& filename) + : size_problem_(0) + , size_(0) + , size_matrix_(0) + , size_missions_(0) + , size_rest_(0) + , size_alternative_relations_(0) + , deliveries_counter_(0) + , horizon_(0) + , earliest_start_(CUSTOM_MAX_INT) + , max_distance_(0) + , max_distance_cost_(0) + , max_rest_(0) + , max_service_(0) + , max_time_(0) + , max_time_cost_(0) + , max_value_(0) + , max_value_cost_(0) + , multiple_tws_counter_(0) + , sum_max_time_(0) + , tws_counter_(0) + , max_coef_service_(0) + , max_coef_setup_(0) { + LoadInstance(filename); } - int64 BuildDistanceMatrix(const ortools_vrp::Matrix& matrix) { - int64 max_distance = 0; - const int32 size_matrix = sqrt(matrix.distance_size()); - for (int64 i = 0; i < size_matrix; ++i) { - for (int64 j = 0; j < size_matrix; ++j) { - const int64 distance = matrix.distance(i * size_matrix + j); - if (distance < CUSTOM_MAX_INT) - max_distance = std::max(max_distance, distance); - SetDistMatrix(i, j) = distance; - } - } - return max_distance; - } - - int64 BuildValueMatrix(const ortools_vrp::Matrix& matrix) { - int64 max_value = 0; - const int32 size_matrix = sqrt(matrix.value_size()); - for (int64 i = 0; i < size_matrix; ++i) { - for (int64 j = 0; j < size_matrix; ++j) { - const int64 value = matrix.value(i * size_matrix + j); - if (value < CUSTOM_MAX_INT) - max_value = std::max(max_value, value); - SetValueMatrix(i, j) = value; - } - } - return max_value; - } + void LoadInstance(const std::string& filename); int64 Horizon() const { return horizon_; } - int64 MatrixIndex(const RoutingIndexManager::NodeIndex i) const { - return tsptw_clients_[i.value()].matrix_index; - } + int64 EarliestStart() const { return earliest_start_; } int64 MaxTime() const { return max_time_; } @@ -129,8 +98,6 @@ class TSPTWDataDT { int64 MaxValueCost() const { return max_value_cost_; } - int64 TWsCounter() const { return tws_counter_; } - int64 TwiceTWsCounter() const { return multiple_tws_counter_; } int64 DeliveriesCounter() const { return deliveries_counter_; } @@ -168,6 +135,10 @@ class TSPTWDataDT { return tsptw_clients_[i.value()].customer_id; } + std::string PointId(const RoutingIndexManager::NodeIndex i) const { + return tsptw_clients_[i.value()].point_id; + } + int32 ProblemIndex(const RoutingIndexManager::NodeIndex i) const { return tsptw_clients_[i.value()].problem_index; } @@ -176,6 +147,20 @@ class TSPTWDataDT { return tsptw_clients_[i.value()].alternative_index; } + int32 AlternativeActivityIndex(const std::string id) const { + if (alternative_activity_ids_map_.find(id) != alternative_activity_ids_map_.end()) { + return alternative_activity_ids_map_.at(id); + } + return -1; + } + + int32 AlternativeActivitySize(const std::string id) const { + if (alternative_activity_size_map_.find(id) != alternative_activity_size_map_.end()) { + return alternative_activity_size_map_.at(id); + } + return 0; + } + const std::vector& ReadyTime(const RoutingIndexManager::NodeIndex i) const { return tsptw_clients_[i.value()].ready_time; } @@ -185,7 +170,7 @@ class TSPTWDataDT { } bool AllServicesHaveEnd() const { - for (int i = 0; i < tsptw_clients_.size(); i++) { + for (std::size_t i = 0; i < tsptw_clients_.size(); i++) { if (tsptw_clients_[i].due_time.size() == 0) return false; } @@ -227,12 +212,6 @@ class TSPTWDataDT { return tsptw_clients_[i.value()].vehicle_indices; } - void SetVehicleIndices(const int64 i, std::vector&& vehicle_indices) { - tsptw_clients_[i].vehicle_indices = std::move(vehicle_indices); - } - - int32 TimeWindowsSize(const int i) const { return tws_size_[i]; } - int32 Size() const { return size_; } int32 SizeMissions() const { return size_missions_; } @@ -243,6 +222,8 @@ class TSPTWDataDT { int32 SizeRest() const { return size_rest_; } + int32 SizeAlternativeRelations() const { return size_alternative_relations_; } + const std::vector& RefillQuantities(const RoutingIndexManager::NodeIndex i) const { return tsptw_clients_[i.value()].refill_quantities; @@ -273,13 +254,12 @@ class TSPTWDataDT { } std::vector MaxTimes(const ortools_vrp::Matrix& matrix) const { - int64 max_row; - int32 size_matrix = sqrt(matrix.time_size()); + const uint32 matrix_size = matrix.size(); std::vector max_times; - for (int32 i = 0; i < size_matrix; i++) { - max_row = 0; - for (int32 j = 0; j < size_matrix; j++) { - int64 cell = matrix.time(i * size_matrix + j); + for (uint32 i = 0; i < matrix_size; i++) { + int64 max_row = 0; + for (uint32 j = 0; j < matrix_size; j++) { + int64 cell = matrix.time(i * matrix_size + j); if (cell + 0.5 < CUSTOM_MAX_INT) max_row = std::max(max_row, (int64)(cell + 0.5)); } @@ -295,162 +275,144 @@ class TSPTWDataDT { , due_time(std::min(CUSTOM_MAX_INT, due_t)) , duration(dur) {} std::string rest_id; - int64 ready_time; - int64 due_time; - int64 duration; + uint32 ready_time; + uint32 due_time; + uint32 duration; }; struct Vehicle { - Vehicle(TSPTWDataDT* data_, int32 size_) + Vehicle(TSPTWDataDT* data_, int32 size_, const ortools_vrp::Matrix& matrix_, + const ortools_vrp::Matrix& value_matrix_) : data(data_) , size(size_) - , problem_matrix_index(0) - , value_matrix_index(0) - , vehicle_indices(0) + , matrix(&matrix_) + , value_matrix(&value_matrix_) + , start_point_id("") + , matrix_indices(0) , initial_capacity(0) , initial_load(0) , capacity(0) , overload_multiplier(0) , break_size(0) - , max_interval_between_breaks(0) - , max_interval_between_breaks_UB(0) - , max_interval_between_breaks_LB(0) , time_start(0) , time_end(0) , time_maximum_lateness(CUSTOM_MAX_INT) , late_multiplier(0) {} - int32 SizeMatrix() const { return size_matrix; } - - int32 SizeRest() const { return size_rest; } - void SetStart(const RoutingIndexManager::NodeIndex s) { - CHECK_LT(s, size); + DCHECK_LT(s, size); start = s; } void SetStop(const RoutingIndexManager::NodeIndex s) { - CHECK_LT(s, size); + DCHECK_LT(s, size); stop = s; } - int64 ReturnZero(const RoutingIndexManager::NodeIndex, - const RoutingIndexManager::NodeIndex) const { - return 0; + inline int MatrixIndex(const RoutingIndexManager::NodeIndex i, + const RoutingIndexManager::NodeIndex j, + const size_t matrix_size) const { + CheckNodeIsValid(i); + CheckNodeIsValid(j); + const auto i_matrix_index = matrix_indices[i.value()]; + const auto j_matrix_index = matrix_indices[j.value()]; + + if (i_matrix_index == -1 || j_matrix_index == -1) + return -1; + + DCHECK_LT(i_matrix_index, matrix_size); + DCHECK_LT(j_matrix_index, matrix_size); + + return i_matrix_index * matrix_size + j_matrix_index; } int64 Distance(const RoutingIndexManager::NodeIndex i, const RoutingIndexManager::NodeIndex j) const { - CheckNodeIsValid(i); - CheckNodeIsValid(j); - if (vehicle_indices[i.value()] == -1 || vehicle_indices[j.value()] == -1) + const auto index = MatrixIndex(i, j, matrix->size()); + if (index == -1) return 0; - if (i != Start() && j != Stop() && max_ride_distance_ > 0 && - data->distances_matrices_[problem_matrix_index].Cost( - RoutingIndexManager::NodeIndex(vehicle_indices[i.value()]), - RoutingIndexManager::NodeIndex(vehicle_indices[j.value()])) > - max_ride_distance_) - return CUSTOM_MAX_INT; - return data->distances_matrices_[problem_matrix_index].Cost( - RoutingIndexManager::NodeIndex(vehicle_indices[i.value()]), - RoutingIndexManager::NodeIndex(vehicle_indices[j.value()])); - } - int64 FakeDistance(const RoutingIndexManager::NodeIndex i, - const RoutingIndexManager::NodeIndex j) const { - CheckNodeIsValid(i); - CheckNodeIsValid(j); - if (vehicle_indices[i.value()] == -1 || vehicle_indices[j.value()] == -1 || - (i == Start() && free_approach) || (j == Stop() && free_return)) - return 0; - if (i != Start() && j != Stop() && max_ride_distance_ > 0 && - data->distances_matrices_[problem_matrix_index].Cost( - RoutingIndexManager::NodeIndex(vehicle_indices[i.value()]), - RoutingIndexManager::NodeIndex(vehicle_indices[j.value()])) > - max_ride_distance_) + const auto dist = matrix->distance(index); + + if (max_ride_distance_ > 0 && i != Start() && j != Stop() && + dist > max_ride_distance_) return CUSTOM_MAX_INT; - return data->distances_matrices_[problem_matrix_index].Cost( - RoutingIndexManager::NodeIndex(vehicle_indices[i.value()]), - RoutingIndexManager::NodeIndex(vehicle_indices[j.value()])); + + return dist; } int64 Time(const RoutingIndexManager::NodeIndex i, const RoutingIndexManager::NodeIndex j) const { - CheckNodeIsValid(i); - CheckNodeIsValid(j); - if (vehicle_indices[i.value()] == -1 || vehicle_indices[j.value()] == -1) + const auto index = MatrixIndex(i, j, matrix->size()); + if (index == -1) return 0; - if (i != Start() && j != Stop() && max_ride_time_ > 0 && - data->times_matrices_[problem_matrix_index].Cost( - RoutingIndexManager::NodeIndex(vehicle_indices[i.value()]), - RoutingIndexManager::NodeIndex(vehicle_indices[j.value()])) > - max_ride_time_) + + const auto time = matrix->time(index); + + if (max_ride_time_ > 0 && i != Start() && j != Stop() && time > max_ride_time_) return CUSTOM_MAX_INT; - return data->times_matrices_[problem_matrix_index].Cost( - RoutingIndexManager::NodeIndex(vehicle_indices[i.value()]), - RoutingIndexManager::NodeIndex(vehicle_indices[j.value()])); + + return time; + } + + int64 Value(const RoutingIndexManager::NodeIndex i, + const RoutingIndexManager::NodeIndex j) const { + const auto index = MatrixIndex(i, j, value_matrix->size()); + if (index == -1) + return 0; + + return value_matrix->value(index); } int64 FakeTime(const RoutingIndexManager::NodeIndex i, const RoutingIndexManager::NodeIndex j) const { - CheckNodeIsValid(i); - CheckNodeIsValid(j); - if (vehicle_indices[i.value()] == -1 || vehicle_indices[j.value()] == -1 || - (i == Start() && free_approach) || (j == Stop() && free_return)) + if ((i == Start() && free_approach) || (j == Stop() && free_return)) return 0; - if (i != Start() && j != Stop() && max_ride_time_ > 0 && - data->times_matrices_[problem_matrix_index].Cost( - RoutingIndexManager::NodeIndex(vehicle_indices[i.value()]), - RoutingIndexManager::NodeIndex(vehicle_indices[j.value()])) > - max_ride_time_) - return CUSTOM_MAX_INT; - return data->times_matrices_[problem_matrix_index].Cost( - RoutingIndexManager::NodeIndex(vehicle_indices[i.value()]), - RoutingIndexManager::NodeIndex(vehicle_indices[j.value()])); + + return Time(i, j); } - int64 Value(const RoutingIndexManager::NodeIndex i, - const RoutingIndexManager::NodeIndex j) const { - CheckNodeIsValid(i); - CheckNodeIsValid(j); - if (vehicle_indices[i.value()] == -1 || vehicle_indices[j.value()] == -1) + int64 FakeDistance(const RoutingIndexManager::NodeIndex i, + const RoutingIndexManager::NodeIndex j) const { + if ((i == Start() && free_approach) || (j == Stop() && free_return)) return 0; - return data->values_matrices_[value_matrix_index].Cost( - RoutingIndexManager::NodeIndex(vehicle_indices[i.value()]), - RoutingIndexManager::NodeIndex(vehicle_indices[j.value()])); + + return Distance(i, j); } int64 TimeOrder(const RoutingIndexManager::NodeIndex i, const RoutingIndexManager::NodeIndex j) const { - CheckNodeIsValid(i); - CheckNodeIsValid(j); - if (vehicle_indices[i.value()] == -1 || vehicle_indices[j.value()] == -1) - return 0; - return 10 * std::sqrt(data->times_matrices_[problem_matrix_index].Cost( - RoutingIndexManager::NodeIndex(vehicle_indices[i.value()]), - RoutingIndexManager::NodeIndex(vehicle_indices[j.value()]))); + return 10 * std::sqrt(Time(i, j)); } int64 DistanceOrder(const RoutingIndexManager::NodeIndex i, const RoutingIndexManager::NodeIndex j) const { - CheckNodeIsValid(i); - CheckNodeIsValid(j); - if (vehicle_indices[i.value()] == -1 || vehicle_indices[j.value()] == -1) - return 0; - return 100 * std::sqrt(data->distances_matrices_[problem_matrix_index].Cost( - RoutingIndexManager::NodeIndex(vehicle_indices[i.value()]), - RoutingIndexManager::NodeIndex(vehicle_indices[j.value()]))); + return 100 * std::sqrt(Distance(i, j)); } // Transit quantity at a node "from" // This is the quantity added after visiting node "from" int64 TimePlusServiceTime(const RoutingIndexManager::NodeIndex from, const RoutingIndexManager::NodeIndex to) const { - return Time(from, to) + coef_service * data->ServiceTime(from) + - additional_service + - (Time(from, to) > 0 ? coef_setup * data->SetupTime(to) + - (data->SetupTime(to) > 0 ? additional_setup : 0) - : 0); + std::string from_id = + from.value() > data->SizeMissions() ? start_point_id : data->PointId(from); + int64 current_time = Time(from, to) + coef_service * data->ServiceTime(from) + + additional_service + + (from_id != data->PointId(to) + ? coef_setup * data->SetupTime(to) + + (data->SetupTime(to) > 0 ? additional_setup : 0) + : 0); + + // In case of order or sequence relations having no duration + // will violate relations as the cumul_var will be the same. + // Moreover with sequence+shipment lead or-tools to try only + // invalid order of nodes + if (current_time == 0 && data->SizeAlternativeRelations() > 0 && + to.value() < data->SizeMissions()) { + ++current_time; + } + + return current_time; // FIXME: // (Time(from, to) == 0 ? 0 // and @@ -461,24 +423,16 @@ class TSPTWDataDT { int64 FakeTimePlusServiceTime(const RoutingIndexManager::NodeIndex from, const RoutingIndexManager::NodeIndex to) const { + std::string from_id = + from.value() > data->SizeMissions() ? start_point_id : data->PointId(from); return FakeTime(from, to) + coef_service * data->ServiceTime(from) + additional_service + - (FakeTime(from, to) > 0 + (from_id != data->PointId(to) ? coef_setup * data->SetupTime(to) + (data->SetupTime(to) > 0 ? additional_setup : 0) : 0); } - int64 ValuePlusServiceValue(const RoutingIndexManager::NodeIndex from, - const RoutingIndexManager::NodeIndex to) const { - return Time(from, to) + data->ServiceValue(from); - } - - int64 TimePlus(const RoutingIndexManager::NodeIndex from, - const RoutingIndexManager::NodeIndex to) const { - return Time(from, to); - } - RoutingIndexManager::NodeIndex Start() const { return start; } RoutingIndexManager::NodeIndex Stop() const { return stop; } @@ -496,13 +450,14 @@ class TSPTWDataDT { std::string id; int64 vehicle_index; int32 size; + const ortools_vrp::Matrix* const matrix; + const ortools_vrp::Matrix* const value_matrix; int32 size_matrix; int32 size_rest; RoutingIndexManager::NodeIndex start; RoutingIndexManager::NodeIndex stop; - int64 problem_matrix_index; - int64 value_matrix_index; - std::vector vehicle_indices; + std::string start_point_id; + std::vector matrix_indices; std::vector initial_capacity; std::vector initial_load; std::vector capacity; @@ -510,9 +465,6 @@ class TSPTWDataDT { std::vector overload_multiplier; std::vector rests; int32 break_size; - int64 max_interval_between_breaks; - int64 max_interval_between_breaks_UB; - int64 max_interval_between_breaks_LB; int64 time_start; int64 time_end; int64 time_maximum_lateness; @@ -530,8 +482,8 @@ class TSPTWDataDT { int64 distance; ShiftPref shift_preference; int32 day_index; - int64 max_ride_time_; - int64 max_ride_distance_; + uint32 max_ride_time_; + uint32 max_ride_distance_; bool free_approach; bool free_return; }; @@ -540,38 +492,12 @@ class TSPTWDataDT { const Vehicle& Vehicles(const int64 index) const { return tsptw_vehicles_[index]; } - int64 MaxBreakDistOfVehicle(const int64 index) const { - return tsptw_vehicles_[index].max_interval_between_breaks; - } - - int64 MaxBreakDistUBOfVehicle(const int64 index) const { - return tsptw_vehicles_[index].max_interval_between_breaks_UB; - } - - int64 MaxBreakDistLBOfVehicle(const int64 index) const { - return tsptw_vehicles_[index].max_interval_between_breaks_LB; - } - - void SetMaxBreakDistOfVehicle(const int64 index, const int64 max_interval) { - tsptw_vehicles_[index].max_interval_between_breaks = max_interval; - } - - void SetMaxBreakDistUBOfVehicle(const int64 index, - const int64 max_interval_upperbound) { - tsptw_vehicles_[index].max_interval_between_breaks_UB = max_interval_upperbound; - } - - void SetMaxBreakDistLBOfVehicle(const int64 index, - const int64 max_interval_lowerbound) { - tsptw_vehicles_[index].max_interval_between_breaks_LB = max_interval_lowerbound; - } - bool VehicleHasEnd(const int64 index) const { return tsptw_vehicles_[index].time_end < CUSTOM_MAX_INT; } bool AllVehiclesHaveEnd() { - for (int v = 0; v < tsptw_vehicles_.size(); v++) { + for (std::size_t v = 0; v < tsptw_vehicles_.size(); v++) { if (!VehicleHasEnd(v)) return false; } @@ -623,8 +549,6 @@ class TSPTWDataDT { const std::vector& Relations() const { return tsptw_relations_; } - const std::vector& VehiclesDay() const { return vehicles_day_; } - int VehicleDay(const int64 index) const { if (index < 0) { return -1; @@ -632,20 +556,16 @@ class TSPTWDataDT { return vehicles_day_[index]; } - int VehicleDayAlt(const int64 index) const { - if (index < 0) { - return CUSTOM_MAX_INT; - } - return vehicles_day_[index]; - } - private: + ortools_vrp::Problem problem; + void ProcessNewLine(char* const line); struct TSPTWClient { // Depot definition - TSPTWClient(std::string cust_id, int32 m_i, int32 p_i) + TSPTWClient(std::string cust_id, std::string p_id, int32 m_i, int32 p_i) : customer_id(cust_id) + , point_id(p_id) , matrix_index(m_i) , problem_index(p_i) , alternative_index(0) @@ -659,12 +579,13 @@ class TSPTWDataDT { , late_multiplier(0) , is_break(false) {} // Mission definition - TSPTWClient(std::string cust_id, int32 m_i, int32 p_i, int32 a_i, + TSPTWClient(std::string cust_id, std::string p_id, int32 m_i, int32 p_i, int32 a_i, std::vector r_t, std::vector d_t, std::vector& max_lateness, double s_t, double s_v, double st_t, int32 p_t, double l_m, std::vector& v_i, std::vector& q, std::vector& s_q, int64 e_c, std::vector& r_q) : customer_id(cust_id) + , point_id(p_id) , matrix_index(m_i) , problem_index(p_i) , alternative_index(a_i) @@ -683,9 +604,11 @@ class TSPTWDataDT { , refill_quantities(r_q) , is_break(false) {} std::string customer_id; + std::string point_id; int32 matrix_index; int32 problem_index; int32 alternative_index; + int32 alternative_activity_index; std::vector ready_time; std::vector due_time; std::vector maximum_lateness; @@ -702,38 +625,39 @@ class TSPTWDataDT { bool is_break; }; + uint32 size_problem_; int32 size_; - int32 size_missions_; int32 size_matrix_; + int32 size_missions_; int32 size_rest_; - uint32 size_problem_; + int32 size_alternative_relations_; + int64 deliveries_counter_; + int64 horizon_; + int64 earliest_start_; + int64 max_distance_; + int64 max_distance_cost_; + int64 max_rest_; + int64 max_service_; + int64 max_time_; + int64 max_time_cost_; + int64 max_value_; + int64 max_value_cost_; + int64 multiple_tws_counter_; + int64 sum_max_time_; + int64 tws_counter_; + float max_coef_service_; + float max_coef_setup_; std::vector tws_size_; std::vector tsptw_vehicles_; std::vector tsptw_relations_; std::vector tsptw_clients_; std::map alternative_size_map_; + std::map alternative_activity_size_map_; + std::map alternative_activity_ids_map_; std::vector tsptw_routes_; - std::vector distances_matrices_; - std::vector times_matrices_; - std::vector values_matrices_; std::vector vehicles_day_; std::vector service_times_; std::string details_; - int64 horizon_; - int64 max_time_; - int64 sum_max_time_; - int64 max_distance_; - int64 max_value_; - int64 max_time_cost_; - int64 max_distance_cost_; - int64 max_value_cost_; - float max_coef_service_; - float max_coef_setup_; - int64 max_service_; - int64 max_rest_; - int64 tws_counter_; - int64 deliveries_counter_; - int64 multiple_tws_counter_; std::map ids_map_; std::map vehicle_ids_map_; std::map day_index_to_vehicle_index_; @@ -742,8 +666,6 @@ class TSPTWDataDT { void TSPTWDataDT::LoadInstance(const std::string& filename) { GOOGLE_PROTOBUF_VERIFY_VERSION; - ortools_vrp::Problem problem; - { std::fstream input(filename, std::ios::in | std::ios::binary); if (!problem.ParseFromIstream(&input)) { @@ -751,13 +673,15 @@ void TSPTWDataDT::LoadInstance(const std::string& filename) { } } - int32 node_index = 0; - tws_counter_ = 0; - multiple_tws_counter_ = 0; - deliveries_counter_ = 0; - int32 matrix_index = 0; - size_problem_ = 0; - std::vector matrix_indices; + // compute earliest start first + for (const ortools_vrp::Vehicle& vehicle : problem.vehicles()) { + earliest_start_ = std::min((int64)vehicle.time_window().start(), earliest_start_); + } + + int32 node_index = 0; + int32 matrix_index = 0; + int32 previous_matrix_size = 0; + std::vector service_matrix_indices; for (const ortools_vrp::Service& service : problem.services()) { if (!alternative_size_map_.count(service.problem_index())) alternative_size_map_[service.problem_index()] = 0; @@ -799,46 +723,68 @@ void TSPTWDataDT::LoadInstance(const std::string& filename) { if (service.late_multiplier() > 0) { do { - matrix_indices.push_back(service.matrix_index()); - std::vector start; - if (timewindows.size() > 0 && - timewindows[timewindow_index]->start() > -CUSTOM_MAX_INT) - start.push_back(timewindows[timewindow_index]->start()); - else - start.push_back(-CUSTOM_MAX_INT); - - std::vector end; - if (timewindows.size() > 0 && - timewindows[timewindow_index]->end() < CUSTOM_MAX_INT) - end.push_back(timewindows[timewindow_index]->end()); - else - end.push_back(CUSTOM_MAX_INT); - - std::vector max_lateness; - if (timewindows.size() > 0 && - timewindows[timewindow_index]->maximum_lateness() < CUSTOM_MAX_INT) - max_lateness.push_back(timewindows[timewindow_index]->maximum_lateness()); - else - max_lateness.push_back(CUSTOM_MAX_INT); - - size_problem_ = std::max(size_problem_, service.problem_index()); - tsptw_clients_.push_back(TSPTWClient( - (std::string)service.id(), matrix_index, service.problem_index(), - alternative_size_map_[service.problem_index()], start, end, max_lateness, - service.duration(), service.additional_value(), service.setup_duration(), - service.priority(), - timewindows.size() > 0 - ? (int64)(service.late_multiplier() * CUSTOM_BIGNUM_COST) - : 0, - v_i, q, s_q, - service.exclusion_cost() > 0 ? service.exclusion_cost() * CUSTOM_BIGNUM_COST - : -1, - r_q)); - - service_times_.push_back(service.duration()); - alternative_size_map_[service.problem_index()] += 1; - ids_map_[(std::string)service.id()] = node_index; - node_index++; + if (timewindows.size() == 0 || + (earliest_start_ < timewindows[timewindow_index]->end() + + timewindows[timewindow_index]->maximum_lateness())) { + service_matrix_indices.push_back(service.matrix_index()); + std::vector start; + if (timewindows.size() > 0 && + (timewindows[timewindow_index]->start() - earliest_start_) > 0) + start.push_back(timewindows[timewindow_index]->start() - earliest_start_); + else + start.push_back(0); + + std::vector end; + if (timewindows.size() > 0 && + (timewindows[timewindow_index]->end() - earliest_start_) < CUSTOM_MAX_INT) + end.push_back(timewindows[timewindow_index]->end() - earliest_start_); + else + end.push_back(CUSTOM_MAX_INT); + + std::vector max_lateness; + if (timewindows.size() > 0 && + timewindows[timewindow_index]->maximum_lateness() < CUSTOM_MAX_INT) + max_lateness.push_back(timewindows[timewindow_index]->maximum_lateness()); + else + max_lateness.push_back(CUSTOM_MAX_INT); + + size_problem_ = std::max(size_problem_, service.problem_index()); + tsptw_clients_.push_back(TSPTWClient( + (std::string)service.id(), (std::string)service.point_id(), matrix_index, + service.problem_index(), alternative_size_map_[service.problem_index()], + start, end, max_lateness, service.duration(), service.additional_value(), + service.setup_duration(), service.priority(), + timewindows.size() > 0 + ? (int64)(service.late_multiplier() * CUSTOM_BIGNUM_COST) + : 0, + v_i, q, s_q, + service.exclusion_cost() > 0 ? service.exclusion_cost() * CUSTOM_BIGNUM_COST + : -1, + r_q)); + + service_times_.push_back(service.duration()); + alternative_size_map_[service.problem_index()] += 1; + if (ids_map_.find((std::string)service.id()) == ids_map_.end()) + ids_map_[(std::string)service.id()] = node_index; + + if (alternative_activity_ids_map_.find( + (std::string)service.id() + "#" + + std::to_string(service.alternative_index())) == + alternative_activity_ids_map_.end()) { + alternative_activity_ids_map_[service.id() + "#" + + std::to_string(service.alternative_index())] = + node_index; + alternative_activity_size_map_[service.id() + "#" + + std::to_string(service.alternative_index())] = + 1; + } else { + alternative_activity_size_map_[service.id() + "#" + + std::to_string(service.alternative_index())] += + 1; + } + + node_index++; + } ++timewindow_index; } while (timewindow_index < service.time_windows_size()); } else { @@ -847,37 +793,60 @@ void TSPTWDataDT::LoadInstance(const std::string& filename) { std::vector max_lateness; for (const ortools_vrp::TimeWindow* timewindow : timewindows) { - timewindow->start() > -CUSTOM_MAX_INT ? ready_time.push_back(timewindow->start()) - : ready_time.push_back(-CUSTOM_MAX_INT); - timewindow->end() < CUSTOM_MAX_INT ? due_time.push_back(timewindow->end()) - : due_time.push_back(CUSTOM_MAX_INT); - timewindow->maximum_lateness() < CUSTOM_MAX_INT - ? max_lateness.push_back(timewindow->maximum_lateness()) - : max_lateness.push_back(CUSTOM_MAX_INT); + if (earliest_start_ < timewindow->end()) { + (timewindow->start() - earliest_start_) > 0 + ? ready_time.push_back(timewindow->start() - earliest_start_) + : ready_time.push_back(0); + (timewindow->end() - earliest_start_) < CUSTOM_MAX_INT + ? due_time.push_back(timewindow->end() - earliest_start_) + : due_time.push_back(CUSTOM_MAX_INT); + timewindow->maximum_lateness() < CUSTOM_MAX_INT + ? max_lateness.push_back(timewindow->maximum_lateness()) + : max_lateness.push_back(CUSTOM_MAX_INT); + } } - matrix_indices.push_back(service.matrix_index()); + service_matrix_indices.push_back(service.matrix_index()); size_problem_ = std::max(size_problem_, service.problem_index()); tsptw_clients_.push_back(TSPTWClient( - (std::string)service.id(), matrix_index, service.problem_index(), - alternative_size_map_[service.problem_index()], ready_time, due_time, - max_lateness, service.duration(), service.additional_value(), - service.setup_duration(), service.priority(), - timewindows.size() > 0 ? (int64)(service.late_multiplier() * CUSTOM_BIGNUM_COST) - : 0, + (std::string)service.id(), (std::string)service.point_id(), matrix_index, + service.problem_index(), alternative_size_map_[service.problem_index()], + ready_time, due_time, max_lateness, service.duration(), + service.additional_value(), service.setup_duration(), service.priority(), + ready_time.size() > 0 ? (int64)(service.late_multiplier() * CUSTOM_BIGNUM_COST) + : 0, v_i, q, s_q, service.exclusion_cost() > 0 ? service.exclusion_cost() * CUSTOM_BIGNUM_COST : -1, r_q)); service_times_.push_back(service.duration()); alternative_size_map_[service.problem_index()] += 1; - ids_map_[(std::string)service.id()] = node_index; + if (ids_map_.find((std::string)service.id()) == ids_map_.end()) + ids_map_[(std::string)service.id()] = node_index; + + if (alternative_activity_ids_map_.find( + (std::string)service.id() + "#" + + std::to_string(service.alternative_index())) == + alternative_activity_ids_map_.end()) { + alternative_activity_ids_map_[service.id() + "#" + + std::to_string(service.alternative_index())] = + node_index; + alternative_activity_size_map_[service.id() + "#" + + std::to_string(service.alternative_index())] = 1; + } else { + alternative_activity_size_map_[service.id() + "#" + + std::to_string(service.alternative_index())] += 1; + } node_index++; } + if (previous_matrix_size == (int32)service_matrix_indices.size()) { + throw std::invalid_argument( + "A Service transmitted should always lead to at least one Node"); + } + previous_matrix_size = service_matrix_indices.size(); ++matrix_index; } - size_rest_ = 0; for (const ortools_vrp::Vehicle& vehicle : problem.vehicles()) { service_times_.push_back(0); service_times_.push_back(0); @@ -888,54 +857,37 @@ void TSPTWDataDT::LoadInstance(const std::string& filename) { size_missions_ = node_index; size_ = node_index + 2; - max_time_ = 0; - max_distance_ = 0; - max_value_ = 0; - max_time_cost_ = 0; - max_distance_cost_ = 0; - max_value_cost_ = 0; - sum_max_time_ = 0; - for (const ortools_vrp::Matrix& matrix : problem.matrices()) { - // + 2 In case vehicles have no depots - int32 problem_size = - std::max(std::max(sqrt(matrix.distance_size()), sqrt(matrix.time_size())), - sqrt(matrix.value_size())) + - 2 + (size_rest_ > 0 ? 1 : 0); - - distances_matrices_.emplace_back(std::max(problem_size, 3)); - times_matrices_.emplace_back(std::max(problem_size, 3)); - values_matrices_.emplace_back(std::max(problem_size, 3)); - - // Matrix default values - for (int64 i = 0; i < std::max(problem_size, 3); ++i) { - for (int64 j = 0; j < std::max(problem_size, 3); ++j) { - SetTimeMatrix(i, j) = 0; - SetDistMatrix(i, j) = 0; - SetValueMatrix(i, j) = 0; - } - } - - if (matrix.time_size() > 0) { - max_time_ = std::max(max_time_, BuildTimeMatrix(matrix)); - } - // Estimate necessary horizon due to time matrix std::vector max_times(MaxTimes(matrix)); - int64 matrix_sum_time = 0; - if (sqrt(matrix.time_size()) > 0) { - for (int64 i = 0; i < matrix_indices.size(); i++) { - matrix_sum_time += max_times.at(matrix_indices[i]); + int64 matrix_sum_time = 0; + if (matrix.size() > 0) { + const int64 max_time = std::round(*std::max_element( + max_times.begin(), max_times.end(), compare_less_than_custom_max_int)); + + if (max_time < CUSTOM_MAX_INT) + max_time_ = std::max(max_time_, max_time); + + for (std::size_t i = 0; i < service_matrix_indices.size(); i++) { + matrix_sum_time += max_times.at(service_matrix_indices[i]); } } sum_max_time_ = std::max(sum_max_time_, matrix_sum_time); if (matrix.distance_size() > 0) { - max_distance_ = std::max(max_distance_, BuildDistanceMatrix(matrix)); + const int64 max_distance = + std::round(*std::max_element(matrix.distance().begin(), matrix.distance().end(), + compare_less_than_custom_max_int)); + if (max_distance < CUSTOM_MAX_INT) + max_distance_ = std::max(max_distance_, max_distance); } if (matrix.value_size() > 0) { - max_value_ = std::max(max_value_, BuildValueMatrix(matrix)); + const int64 max_value = + std::round(*std::max_element(matrix.value().begin(), matrix.value().end(), + compare_less_than_custom_max_int)); + if (max_value < CUSTOM_MAX_INT) + max_value_ = std::max(max_value_, max_value); } } @@ -946,13 +898,19 @@ void TSPTWDataDT::LoadInstance(const std::string& filename) { int v_idx = 0; day_index_to_vehicle_index_[0] = v_idx; for (const ortools_vrp::Vehicle& vehicle : problem.vehicles()) { - tsptw_vehicles_.emplace_back(this, size_); + if (!vehicle.has_time_window()) { + throw std::invalid_argument( + "A vehicle should always have an initialized timewindow"); + } + + tsptw_vehicles_.emplace_back(this, size_, problem.matrices(vehicle.matrix_index()), + problem.matrices(vehicle.value_matrix_index())); auto v = tsptw_vehicles_.rbegin(); // Every vehicle has its own matrix definition - std::vector vehicle_indices(matrix_indices); - vehicle_indices.push_back(vehicle.start_index()); - vehicle_indices.push_back(vehicle.end_index()); + std::vector matrix_indices(service_matrix_indices); + matrix_indices.push_back(vehicle.start_index()); + matrix_indices.push_back(vehicle.end_index()); for (const ortools_vrp::Capacity& capacity : vehicle.capacities()) { v->capacity.push_back(std::round(capacity.limit() * CUSTOM_BIGNUM_QUANTITY)); @@ -968,17 +926,16 @@ void TSPTWDataDT::LoadInstance(const std::string& filename) { v->counting.push_back(capacity.counting()); } - v->id = vehicle.id(); - v->vehicle_index = v_idx; - v->break_size = vehicle.rests().size(); - v->problem_matrix_index = vehicle.matrix_index(); - v->value_matrix_index = vehicle.value_matrix_index(); - v->vehicle_indices = vehicle_indices; - v->time_start = vehicle.time_window().start() > -CUSTOM_MAX_INT - ? vehicle.time_window().start() - : -CUSTOM_MAX_INT; - v->time_end = vehicle.time_window().end() < CUSTOM_MAX_INT - ? vehicle.time_window().end() + v->id = vehicle.id(); + v->vehicle_index = v_idx; + v->break_size = vehicle.rests().size(); + v->start_point_id = vehicle.start_point_id(); + v->matrix_indices = matrix_indices; + v->time_start = (vehicle.time_window().start() - earliest_start_) > 0 + ? vehicle.time_window().start() - earliest_start_ + : 0; + v->time_end = (vehicle.time_window().end() - earliest_start_) < CUSTOM_MAX_INT + ? vehicle.time_window().end() - earliest_start_ : CUSTOM_MAX_INT; v->time_maximum_lateness = vehicle.time_window().maximum_lateness() < CUSTOM_MAX_INT ? vehicle.time_window().maximum_lateness() @@ -1001,7 +958,7 @@ void TSPTWDataDT::LoadInstance(const std::string& filename) { max_coef_setup_ = std::max(max_coef_setup_, v->coef_setup); v->additional_setup = vehicle.additional_setup(); - v->duration = (int64)(vehicle.duration()); + v->duration = vehicle.duration(); v->distance = vehicle.distance(); v->free_approach = vehicle.free_approach(); v->free_return = vehicle.free_return(); @@ -1030,8 +987,9 @@ void TSPTWDataDT::LoadInstance(const std::string& filename) { // Add vehicle rests for (const ortools_vrp::Rest& rest : vehicle.rests()) { - v->rests.emplace_back((std::string)rest.id(), rest.time_window().start(), - rest.time_window().end(), rest.duration()); + v->rests.emplace_back((std::string)rest.id(), + rest.time_window().start() - earliest_start_, + rest.time_window().end() - earliest_start_, rest.duration()); } v_idx++; @@ -1055,7 +1013,8 @@ void TSPTWDataDT::LoadInstance(const std::string& filename) { for (Vehicle& v : tsptw_vehicles_) { v.start = RoutingIndexManager::NodeIndex(node_index); } - tsptw_clients_.push_back(TSPTWClient("vehicles_start", matrix_index, node_index)); + tsptw_clients_.push_back( + TSPTWClient("vehicles_start", "vehicles_start", matrix_index, node_index)); service_times_.push_back(0); node_index++; @@ -1064,36 +1023,45 @@ void TSPTWDataDT::LoadInstance(const std::string& filename) { v.stop = RoutingIndexManager::NodeIndex(node_index); } // node_index++; - tsptw_clients_.push_back(TSPTWClient("vehicles_end", ++matrix_index, node_index)); + tsptw_clients_.push_back( + TSPTWClient("vehicles_end", "vehicles_end", ++matrix_index, node_index)); service_times_.push_back(0); for (const ortools_vrp::Relation& relation : problem.relations()) { RelationType relType; - if (relation.type() == "sequence") + if (relation.type() == "sequence") { + ++size_alternative_relations_; relType = Sequence; - else if (relation.type() == "order") { + } else if (relation.type() == "order") { + ++size_alternative_relations_; relType = Order; - } else if (relation.type() == "same_route") + } else if (relation.type() == "same_route") { + ++size_alternative_relations_; relType = SameRoute; - else if (relation.type() == "minimum_day_lapse") + } else if (relation.type() == "minimum_day_lapse") relType = MinimumDayLapse; else if (relation.type() == "maximum_day_lapse") relType = MaximumDayLapse; - else if (relation.type() == "shipment") + else if (relation.type() == "shipment") { + ++size_alternative_relations_; relType = Shipment; - else if (relation.type() == "meetup") + } else if (relation.type() == "meetup") relType = MeetUp; else if (relation.type() == "maximum_duration_lapse") relType = MaximumDurationLapse; - else if (relation.type() == "force_first") + else if (relation.type() == "force_first") { + ++size_alternative_relations_; relType = ForceFirst; - else if (relation.type() == "never_first") + } else if (relation.type() == "never_first") { + ++size_alternative_relations_; relType = NeverFirst; - else if (relation.type() == "never_last") + } else if (relation.type() == "never_last") { + ++size_alternative_relations_; relType = NeverLast; - else if (relation.type() == "force_end") + } else if (relation.type() == "force_end") { + ++size_alternative_relations_; relType = ForceLast; - else if (relation.type() == "vehicle_group_duration") + } else if (relation.type() == "vehicle_group_duration") relType = VehicleGroupDuration; else if (relation.type() == "vehicle_trips") { relType = VehicleTrips; @@ -1103,7 +1071,7 @@ void TSPTWDataDT::LoadInstance(const std::string& filename) { else if (relation.type() == "minimum_duration_lapse") relType = MinimumDurationLapse; else - throw "Unknown relation type"; + throw std::invalid_argument("Unknown relation type"); sum_lapse += relation.lapse(); tsptw_relations_.emplace_back(re_index, relType, relation.linked_ids(), @@ -1112,30 +1080,27 @@ void TSPTWDataDT::LoadInstance(const std::string& filename) { } // Compute horizon - horizon_ = 0; - max_service_ = 0; - max_rest_ = 0; int64 rest_duration; - for (int32 v = 0; v < tsptw_vehicles_.size(); v++) { + for (std::size_t v = 0; v < tsptw_vehicles_.size(); v++) { rest_duration = 0; - for (int32 r = 0; r < tsptw_vehicles_[v].Rests().size(); r++) { + for (std::size_t r = 0; r < tsptw_vehicles_[v].Rests().size(); r++) { rest_duration += tsptw_vehicles_[v].Rests()[r].duration; } max_rest_ = std::max(max_rest_, rest_duration); } if (AllVehiclesHaveEnd()) { - for (int32 v = 0; v < tsptw_vehicles_.size(); v++) { + for (std::size_t v = 0; v < tsptw_vehicles_.size(); v++) { horizon_ = std::max(horizon_, tsptw_vehicles_[v].time_end + tsptw_vehicles_[v].time_maximum_lateness); } } else if (AllServicesHaveEnd()) { - for (int32 i = 0; i < tsptw_clients_.size(); i++) { + for (std::size_t i = 0; i < tsptw_clients_.size(); i++) { horizon_ = std::max(horizon_, tsptw_clients_[i].due_time.back() + tsptw_clients_[i].maximum_lateness.back()); } - for (int32 v = 0; v < tsptw_vehicles_.size(); v++) { - for (int32 r = 0; r < tsptw_vehicles_[v].Rests().size(); r++) { - horizon_ = std::max(horizon_, tsptw_vehicles_[v].Rests()[r].due_time); + for (std::size_t v = 0; v < tsptw_vehicles_.size(); v++) { + for (std::size_t r = 0; r < tsptw_vehicles_[v].Rests().size(); r++) { + horizon_ = std::max(horizon_, tsptw_vehicles_[v].Rests()[r].due_time); } } } else { @@ -1150,18 +1115,21 @@ void TSPTWDataDT::LoadInstance(const std::string& filename) { latest_start = std::max(latest_start, tsptw_clients_[i].ready_time.back()); } } - for (int32 v = 0; v < tsptw_vehicles_.size(); v++) { + for (std::size_t v = 0; v < tsptw_vehicles_.size(); v++) { latest_start = std::max(latest_start, tsptw_vehicles_[v].time_start); - for (int32 r = 0; r < tsptw_vehicles_[v].Rests().size(); r++) { + for (std::size_t r = 0; r < tsptw_vehicles_[v].Rests().size(); r++) { latest_rest_end = - std::max(latest_rest_end, tsptw_vehicles_[v].Rests()[r].due_time); + std::max(latest_rest_end, tsptw_vehicles_[v].Rests()[r].due_time); } } horizon_ = std::max(latest_start, latest_rest_end) + sum_service * max_coef_service_ + sum_setup * max_coef_setup_ + sum_max_time_ + sum_lapse + max_rest_; } + if (size_alternative_relations_ > 0) + horizon_ += size_missions_; + for (int32 i = 0; i < size_missions_; ++i) { max_service_ = std::max(max_service_, tsptw_clients_[i].service_time); }