diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 06efc988e..a40922a4e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -64,6 +64,12 @@ jobs: echo $(ls -lh vendor/bundle) - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} + registry: ${{ secrets.REGISTRY }} - name: Build uses: docker/build-push-action@v2 with: diff --git a/api/v01/entities/vrp_input.rb b/api/v01/entities/vrp_input.rb index bb9a19cb0..4f59d34d8 100644 --- a/api/v01/entities/vrp_input.rb +++ b/api/v01/entities/vrp_input.rb @@ -177,6 +177,7 @@ module VrpConfiguration optional(:stable_iterations, type: Integer, allow_blank: false, documentation: { hidden: true }, desc: 'DEPRECATED : Jsprit solver and related parameters are not supported anymore') optional(:stable_coefficient, type: Float, allow_blank: false, documentation: { hidden: true }, desc: 'DEPRECATED : Jsprit solver and related parameters are not supported anymore') optional(:initial_time_out, type: Integer, allow_blank: false, documentation: { hidden: true }, desc: '[ DEPRECATED : use minimum_duration instead]') + optional(:strict_skills, type: Boolean, allow_blank: false, documentation: { hidden: true }, desc: 'All skills must be taken into during resolution') optional(:minimum_duration, type: Integer, allow_blank: false, desc: 'Minimum solve duration before the solve could stop (x10 in order to find the first solution) (ORtools only)') optional(:time_out_multiplier, type: Integer, desc: 'The solve could stop itself if the solve duration without finding a new solution is greater than the time currently elapsed multiplicate by this parameter (ORtools only)') optional(:vehicle_limit, type: Integer, desc: 'Limit the maximum number of vehicles within a solution. Not available with periodic heuristic.') diff --git a/docker/Dockerfile b/docker/Dockerfile index ed30cde0c..2518609aa 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -20,6 +20,11 @@ ENV LANG C.UTF-8 # Set correct environment variables. ENV HOME /root +RUN apt update && apt install -y python3.8 python3-pip && \ + pip3 install protobuf==3.20.* && \ + pip3 install schema && \ + pip3 install scikit-learn + # 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 @@ -40,9 +45,14 @@ RUN apt-get update > /dev/null && \ COPY --chown=app . /srv/app/ RUN install -d --owner app /srv/app/archives -USER app +USER root WORKDIR /srv/app +RUN pip3 install unconstrained-initialization/dependencies/fastvrpy-0.5.2.tar.gz --user + + + +USER app RUN gem install bundler --version 2.2.24 && \ bundle --version && \ bundle install --path vendor/bundle --full-index --without ${BUNDLE_WITHOUT} -j $(nproc) diff --git a/lib/filters.rb b/lib/filters.rb index 99901ad01..1ba4ad421 100644 --- a/lib/filters.rb +++ b/lib/filters.rb @@ -19,9 +19,9 @@ module Filters def self.filter(vrp) merge_timewindows(vrp) - - filter_skills(vrp) - + unless vrp.configuration.resolution.strict_skills + filter_skills(vrp) + end # calculate_unit_precision # TODO: treat only input vrp, not all models in memory from other vrps nil end diff --git a/lib/interpreters/split_clustering.rb b/lib/interpreters/split_clustering.rb index 0b208421d..7c1ff9b95 100644 --- a/lib/interpreters/split_clustering.rb +++ b/lib/interpreters/split_clustering.rb @@ -1473,6 +1473,20 @@ def add_duration_from_and_to_depot(vrp, data_items) end log "matrix computed in #{(Time.now - tic).round(2)} seconds" + max_time_from_depot = 0 + max_time_to_depot = 0 + + time_matrix_from_depot.map{ |matrix| + matrix.map{ |time| + ((time > max_time_from_depot) && (time != 2147483647)) ? max_time_from_depot = time : nil + } + } + time_matrix_to_depot.map{ |matrix| + matrix.map{ |time| + ((time > max_time_to_depot) && (time != 2147483647)) ? max_time_to_depot = time : nil + } + } + v_index = { from: vrp.vehicles.collect{ |v| start_loc = v.start_point&.location @@ -1488,8 +1502,10 @@ def add_duration_from_and_to_depot(vrp, data_items) point[4][:duration_from_and_to_depot] = [] vrp.vehicles.each_with_index{ |_vehicle, v_i| - duration_from = time_matrix_from_depot[v_index[:from][v_i]][p_index] if v_index[:from][v_i] - duration_to = time_matrix_to_depot[p_index][v_index[:to][v_i]] if v_index[:to][v_i] + duration_from = [time_matrix_from_depot[v_index[:from][v_i]][p_index], max_time_from_depot * 3].min\ + if v_index[:from][v_i] + duration_to = [time_matrix_to_depot[p_index][v_index[:to][v_i]], max_time_to_depot * 3].min if\ + v_index[:to][v_i] # TODO: investigate why division by vehicle.router_options[:speed_multiplier] # detoriarates the performance of periodic diff --git a/models/configuration.rb b/models/configuration.rb index 5362c532f..b76ce32bf 100644 --- a/models/configuration.rb +++ b/models/configuration.rb @@ -77,6 +77,7 @@ class Resolution < Base field :variation_ratio, default: nil field :batch_heuristic, default: false field :repetition, default: nil + field :strict_skills, default: false # random_seed parameter is set by the API during the POST process before dumping the VRP # it is to make the runs repeatable/reproducible and simplifies comparing different environments diff --git a/test/wrappers/ortools_test.rb b/test/wrappers/ortools_test.rb index 121df9eda..87dd8370a 100644 --- a/test/wrappers/ortools_test.rb +++ b/test/wrappers/ortools_test.rb @@ -5307,4 +5307,69 @@ def test_activities_order unassigned_stops = solution.unassigned_stops assert_equal 2, unassigned_stops.size end + + def test_strict_skills + ortools = OptimizerWrapper.config[:services][:ortools] + problem = { + matrices: [{ + id: 'matrix_0', + time: [ + [0, 1, 1], + [1, 0, 1], + [1, 1, 0] + ] + }], + points: [{ + id: 'point_0', + matrix_index: 0 + }, { + id: 'point_1', + matrix_index: 1 + }, { + id: 'point_2', + matrix_index: 2 + }], + vehicles: [{ + id: 'vehicle_0', + start_point_id: 'point_0', + matrix_id: 'matrix_0' + }], + services: [{ + id: 'service_1', + skills: ['frozen'], + activity: { + point_id: 'point_1' + } + }, { + id: 'service_2', + skills: ['frozen'], + activity: { + point_id: 'point_2' + } + }], + configuration: { + resolution: { + strict_skills: true, + duration: 20, + } + } + } + + vrp_strict = TestHelper.create(problem) + + Filters.filter(vrp_strict) + + solution_strict = ortools.solve(vrp_strict, 'test') + + assert_equal 1, solution_strict.routes.size + assert_equal problem[:services].size, solution_strict.unassigned_stops.size + + problem_unstrict = Marshal.load(Marshal.dump(problem)) + problem_unstrict[:configuration][:resolution][:strict_skills] = false + vrp_unstrict = TestHelper.create(problem_unstrict) + Filters.filter(vrp_unstrict) + solution_unstrict = ortools.solve(vrp_unstrict, 'test') + assert_equal 1, solution_unstrict.routes.size + assert_equal 0, solution_unstrict.unassigned_stops.size + end end diff --git a/test/wrappers/unconstrained_initialization_test.rb b/test/wrappers/unconstrained_initialization_test.rb new file mode 100644 index 000000000..e0d03ebb1 --- /dev/null +++ b/test/wrappers/unconstrained_initialization_test.rb @@ -0,0 +1,604 @@ +# Copyright © Mapotempo, 2016 +# +# This file is part of Mapotempo. +# +# Mapotempo is free software. You can redistribute it and/or +# modify since you respect the terms of the GNU Affero General +# Public License as published by the Free Software Foundation, +# either version 3 of the License, or (at your option) any later version. +# +# Mapotempo is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the Licenses for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Mapotempo. If not, see: +# +# +require './test/test_helper' + +class Wrappers::UnconstrainedInitializationTest < Minitest::Test + def test_cvrptw + unconstrained_initialization = OptimizerWrapper.config[:services][:unconstrained_initialization] + problem = { + matrices: [{ + id: 'matrix_0', + time: [ + [0, 1, 1, 2, 4, 4, 6, 7, 8, 12, 1], + [1, 0, 1, 2, 4, 4, 6, 7, 8, 12, 1], + [1, 1, 0, 2, 4, 4, 6, 7, 8, 12, 1], + [1, 1, 2, 0, 4, 4, 6, 7, 8, 12, 1], + [4, 4, 4, 4, 0, 4, 6, 7, 8, 12, 1], + [4, 4, 4, 4, 3, 0, 6, 7, 8, 12, 1], + [4, 4, 4, 4, 8, 4, 0, 7, 8, 12, 1], + [4, 4, 4, 4, 8, 4, 6, 0, 8, 12, 1], + [4, 4, 4, 4, 8, 4, 6, 7, 0, 12, 1], + [4, 4, 4, 4, 8, 4, 6, 7, 0, 0, 1], + [4, 4, 4, 4, 8, 4, 6, 7, 0, 12, 0] + ] + }], + units: [{ + id: 'unit_0', + }], + points: [{ + id: 'point_0', + matrix_index: 0 + }, { + id: 'point_1', + matrix_index: 1 + }, { + id: 'point_2', + matrix_index: 2 + }, { + id: 'point_3', + matrix_index: 3 + }, { + id: 'point_4', + matrix_index: 4 + }, { + id: 'point_5', + matrix_index: 5 + }, { + id: 'point_6', + matrix_index: 6 + }, { + id: 'point_7', + matrix_index: 7 + }, { + id: 'point_8', + matrix_index: 8 + }, { + id: 'point_9', + matrix_index: 9 + }, { + id: 'point_10', + matrix_index: 10 + }], + vehicles: [{ + id: 'vehicle_0', + cost_fixed: 0, + start_point_id: 'point_0', + end_point_id: 'point_0', + matrix_id: 'matrix_0', + force_start: false, + cost_distance_multiplier: 1, + cost_time_multiplier: 1, + cost_late_multiplier: 0, + capacities: [{ + unit_id: 'unit_0', + limit: 2000, + overload_multiplier: 0, + }], + timewindow: { + start: 0, + end: 100 + } + }, { + id: 'vehicle_1', + cost_fixed: 0, + start_point_id: 'point_0', + end_point_id: 'point_0', + matrix_id: 'matrix_0', + cost_distance_multiplier: 1, + cost_time_multiplier: 1, + cost_late_multiplier: 0, + timewindow: { + start: 0, + end: 100 + }, + capacities: [{ + unit_id: 'unit_0', + limit: 100, + overload_multiplier: 0, + }] + }], + services: [{ + id: 'service_1', + activity: { + duration: 1, + point_id: 'point_1', + timewindows: [{ + start: 0, + end: 80 + }], + }, + quantities: [{ + unit_id: 'unit_0', + value: 80, + }] + }, { + id: 'service_2', + activity: { + duration: 1, + point_id: 'point_3', + timewindows: [{ + start: 0, + end: 80 + }], + }, + quantities: [{ + unit_id: 'unit_0', + value: 8, + }] + }, { + id: 'service_3', + activity: { + point_id: 'point_3', + timewindows: [{ + start: 0, + end: 80 + }], + + duration: 2 + }, + quantities: [{ + unit_id: 'unit_0', + value: 8, + }] + }, { + id: 'service_4', + activity: { + point_id: 'point_4', + timewindows: [{ + start: 0, + end: 80 + }], + + duration: 2 + }, + quantities: [{ + unit_id: 'unit_0', + value: 8, + }] + }, { + id: 'service_5', + activity: { + point_id: 'point_4', + timewindows: [{ + start: 0, + end: 80 + }], + + duration: 2 + }, + quantities: [{ + unit_id: 'unit_0', + value: 8, + }] + }, { + id: 'service_6', + activity: { + point_id: 'point_4', + timewindows: [{ + start: 0, + end: 80 + }], + + duration: 2 + }, + quantities: [{ + unit_id: 'unit_0', + value: 8, + }] + }, { + id: 'service_7', + activity: { + point_id: 'point_5', + timewindows: [{ + start: 0, + end: 80 + }], + + duration: 2 + }, + quantities: [{ + unit_id: 'unit_0', + value: 8, + }] + }, { + id: 'service_8', + activity: { + point_id: 'point_5', + timewindows: [{ + start: 0, + end: 80 + }], + + duration: 2 + }, + quantities: [{ + unit_id: 'unit_0', + value: 8, + }] + }, { + id: 'service_9', + activity: { + point_id: 'point_6', + timewindows: [{ + start: 0, + end: 80 + }], + + duration: 2 + }, + quantities: [{ + unit_id: 'unit_0', + value: 8, + }] + }, { + id: 'service_10', + activity: { + point_id: 'point_6', + timewindows: [{ + start: 0, + end: 80 + }], + + duration: 2 + }, + quantities: [{ + unit_id: 'unit_0', + value: 8, + }] + }, { + id: 'service_11', + activity: { + point_id: 'point_7', + timewindows: [{ + start: 0, + end: 80 + }], + + duration: 2 + }, + quantities: [{ + unit_id: 'unit_0', + value: 8, + }] + }, { + id: 'service_12', + activity: { + point_id: 'point_7', + timewindows: [{ + start: 0, + end: 80 + }], + + duration: 2 + }, + quantities: [{ + unit_id: 'unit_0', + value: 8, + }] + }, { + id: 'service_13', + activity: { + point_id: 'point_8', + timewindows: [{ + start: 0, + end: 80 + }], + + duration: 2 + }, + quantities: [{ + unit_id: 'unit_0', + value: 8, + }] + }, { + id: 'service_14', + activity: { + point_id: 'point_8', + timewindows: [{ + start: 0, + end: 80 + }], + + duration: 2 + }, + quantities: [{ + unit_id: 'unit_0', + value: 8, + }] + }, { + id: 'service_15', + activity: { + point_id: 'point_8', + timewindows: [{ + start: 0, + end: 80 + }], + + duration: 2 + }, + quantities: [{ + unit_id: 'unit_0', + value: 8, + }] + }, { + id: 'service_16', + activity: { + point_id: 'point_8', + timewindows: [{ + start: 0, + end: 80 + }], + + duration: 2 + }, + quantities: [{ + unit_id: 'unit_0', + value: 8, + }] + }, { + id: 'service_17', + activity: { + point_id: 'point_8', + timewindows: [{ + start: 0, + end: 80 + }], + + duration: 2 + }, + quantities: [{ + unit_id: 'unit_0', + value: 8, + }] + }, { + id: 'service_18', + activity: { + point_id: 'point_9', + timewindows: [{ + start: 0, + end: 80 + }], + + duration: 2 + }, + quantities: [{ + unit_id: 'unit_0', + value: 8, + }] + }, { + id: 'service_19', + activity: { + point_id: 'point_9', + timewindows: [{ + start: 0, + end: 80 + }], + + duration: 2 + }, + quantities: [{ + unit_id: 'unit_0', + value: 8, + }] + }, { + id: 'service_20', + activity: { + point_id: 'point_10', + timewindows: [{ + start: 0, + end: 80 + }], + + duration: 2 + }, + quantities: [{ + unit_id: 'unit_0', + value: 8, + }] + }], + configuration: { + resolution: { + duration: 2000, + } + } + } + vrp = TestHelper.create(problem) + solution = unconstrained_initialization.solve(vrp, 'test') + assert solution + assert_equal 2, solution.routes.size + assert_equal problem[:services].size + 2, solution.routes.first.stops.size + end + + def test_cvrptw_2 + unconstrained_initialization = OptimizerWrapper.config[:services][:unconstrained_initialization] + problem = { + matrices: [{ + id: 'matrix_0', + time: [ + [0, 1, 1, 2], + [1, 0, 1, 2], + [1, 1, 0, 2], + [1, 1, 2, 0] + ] + }], + units: [{ + id: 'unit_0', + }], + points: [{ + id: 'point_0', + matrix_index: 0 + }, { + id: 'point_1', + matrix_index: 1 + }, { + id: 'point_2', + matrix_index: 2 + }, { + id: 'point_3', + matrix_index: 3 + }], + vehicles: [{ + id: 'vehicle_0', + cost_fixed: 0, + start_point_id: 'point_0', + end_point_id: 'point_0', + matrix_id: 'matrix_0', + cost_distance_multiplier: 1, + cost_time_multiplier: 1, + timewindow: { + start: 0, + end: 10 + } + }], + services: [{ + id: 'service_1', + activity: { + point_id: 'point_1', + timewindows: [{ + start: 0, + end: 8 + }] + } + }, { + id: 'service_2', + activity: { + point_id: 'point_2', + timewindows: [{ + start: 0, + end: 8 + }] + } + }, { + id: 'service_3', + activity: { + point_id: 'point_2', + timewindows: [{ + start: 0, + end: 8 + }] + } + }, { + id: 'service_4', + activity: { + point_id: 'point_3', + timewindows: [{ + start: 0, + end: 8 + }] + } + }], + configuration: { + resolution: { + duration: 2000, + } + } + } + vrp = TestHelper.create(problem) + solution = unconstrained_initialization.solve(vrp, 'test') + assert solution + assert_equal 1, solution.routes.size + assert_equal problem[:services].size + 2, solution.routes.first.stops.size + end + + def test_route_force_start + ortools = OptimizerWrapper.config[:services][:unconstrained_initialization] + problem = { + matrices: [{ + id: 'matrix_0', + time: [ + [0, 3, 3, 9], + [3, 0, 3, 8], + [3, 3, 0, 8], + [9, 9, 9, 0] + ] + }], + units: [{ + id: 'unit_0', + }], + points: [{ + id: 'point_0', + matrix_index: 0 + }, { + id: 'point_1', + matrix_index: 1 + }, { + id: 'point_2', + matrix_index: 2 + }, { + id: 'point_3', + matrix_index: 3 + }], + vehicles: [{ + id: 'vehicle_0', + cost_time_multiplier: 1, + start_point_id: 'point_0', + end_point_id: 'point_0', + matrix_id: 'matrix_0', + force_start: true + }], + services: [{ + id: 'service_0', + activity: { + point_id: 'point_1', + duration: 0, + timewindows: [{ + start: 9 + }], + late_multiplier: 0, + } + }, { + id: 'service_1', + activity: { + point_id: 'point_2', + duration: 0, + timewindows: [{ + start: 18 + }], + late_multiplier: 0, + } + }, { + id: 'service_2', + activity: { + point_id: 'point_3', + duration: 0, + timewindows: [{ + start: 18 + }], + late_multiplier: 0, + } + }], + configuration: { + preprocessing: { + prefer_short_segment: true + }, + resolution: { + duration: 2000 + }, + restitution: { + intermediate_solutions: false, + } + } + } + vrp = TestHelper.create(problem) + solution = ortools.solve(vrp, 'test') + assert solution + assert_equal 0, solution.unassigned_stops.size + assert_equal 5, solution.routes.first.stops.size + assert_equal 0, solution.routes.first.stops.first.info.begin_time + end +end diff --git a/unconstrained-initialization/__init__.py b/unconstrained-initialization/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/unconstrained-initialization/blackboard.py b/unconstrained-initialization/blackboard.py new file mode 100644 index 000000000..430b63b5a --- /dev/null +++ b/unconstrained-initialization/blackboard.py @@ -0,0 +1,32 @@ + + +# Result class that stores all results for futher calculations +class Blackboard(object): + """Blackboard class that stores data and knowledge sources + + The knowledge sources will always be stored in a list that should not be altered by the user. + The user should however add the specific data structures needed by the model to run in the constructor. + + Attributes + ---------- + knowledge_sources: List of knowledge sources + """ + def __init__(self): + + # Do not alter this line + self.knowledge_sources = [] + + # Specific data structures needed by the model to be completed by the user + #... + + + def add_knowledge_source(self, knowledge_source): + """Adds a new knowlegde source to the blackboard + + CAUTION : The user does not need to make any changes in this function. + + Attributes + ---------- + knowledge_source (knowledge source): knowledge source to be added + """ + self.knowledge_sources.append(knowledge_source) \ No newline at end of file diff --git a/unconstrained-initialization/blackboard/__init__.py b/unconstrained-initialization/blackboard/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/unconstrained-initialization/blackboard/blackboard.py b/unconstrained-initialization/blackboard/blackboard.py new file mode 100644 index 000000000..51162c4a7 --- /dev/null +++ b/unconstrained-initialization/blackboard/blackboard.py @@ -0,0 +1,70 @@ + + +# Result class that stores all results for futher calculations +class Blackboard(object): + """Blackboard class that stores data and knowledge sources + + The knowledge sources will always be stored in a list that should not be altered by the user. + The user should however add the specific data structures needed by the model to run in the constructor. + + Attributes + ---------- + knowledge_sources: List of knowledge sources + """ + def __init__(self): + + # Do not alter this line + self.knowledge_sources = [] + + # Specific data structures needed by the model to be completed by the user + #... + self.solution = None + self.time_limit = None + self.instance = None + self.output_file = None + self.problem = None + self.max_capacity = None + self.distance_matrices = None + self.time_matrices = None + self.start_tw = None + self.end_tw = None + self.services_max_lateness = None + self.durations = None + self.setup_durations = None + self.services_volume = None + self.size = None + self.num_vehicle = None + self.max_capacity = None + self.cost_time_multiplier = None + self.cost_distance_multiplier = None + self.vehicle_capacities = None + self.vehicles_TW_starts = None + self.vehicles_TW_ends = None + self.vehicles_distance_max = None + self.vehicles_fixed_costs = None + self.vehicles_overload_multiplier = None + self.previous_vehicle = None + self.paths = None + self.vehicle_start_index = None + self.vehicle_end_index = None + self.service_matrix_index = None + self.num_service = None + self.service_id_to_index_in_problem = None + self.service_sticky_vehicles = None + self.force_start = None + self.free_approach = None + self.force_return = None + self.rests = None + self.vehicle_id_index = None + self.vehicle_time_window_margin = None + + def add_knowledge_source(self, knowledge_source): + """Adds a new knowlegde source to the blackboard + + CAUTION : The user does not need to make any changes in this function. + + Attributes + ---------- + knowledge_source (knowledge source): knowledge source to be added + """ + self.knowledge_sources.append(knowledge_source) diff --git a/unconstrained-initialization/controller/__init__.py b/unconstrained-initialization/controller/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/unconstrained-initialization/controller/controller.py b/unconstrained-initialization/controller/controller.py new file mode 100644 index 000000000..c8c71e63f --- /dev/null +++ b/unconstrained-initialization/controller/controller.py @@ -0,0 +1,30 @@ +import logging as log +log = log.getLogger("controller") + +class Controller(object): + """Controller that handles calling the knowledge sources + + CAUTION : The user does not need to make any changes in this file. + + Attributes + ---------- + balckboard (Blackboard): blackboard containing data and the knowledge sources + """ + + def __init__(self, blackboard): + self.blackboard = blackboard + + def run_knowledge_sources(self): + """For every knowledge source, run the Verify and Process methods + """ + for knowledge_source in self.blackboard.knowledge_sources: + ks_name = type(knowledge_source).__name__ + log.info(f"Running knowledge source {ks_name}") + + # Verify that the knowledge source can be executed + assert knowledge_source.verify() + log.info(f"Verify {ks_name} : OK -> start processing") + + # Execute the knowledge source + knowledge_source.process() + log.info(f"End Processing {ks_name}") \ No newline at end of file diff --git a/unconstrained-initialization/dependencies/fastvrpy-0.1.0.tar.gz b/unconstrained-initialization/dependencies/fastvrpy-0.1.0.tar.gz deleted file mode 100644 index fd0cc510f..000000000 Binary files a/unconstrained-initialization/dependencies/fastvrpy-0.1.0.tar.gz and /dev/null differ diff --git a/unconstrained-initialization/dependencies/fastvrpy-0.5.2.tar.gz b/unconstrained-initialization/dependencies/fastvrpy-0.5.2.tar.gz new file mode 100644 index 000000000..c54f56a4f Binary files /dev/null and b/unconstrained-initialization/dependencies/fastvrpy-0.5.2.tar.gz differ diff --git a/unconstrained-initialization/knowledge_sources/__init__.py b/unconstrained-initialization/knowledge_sources/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/unconstrained-initialization/knowledge_sources/abstract_knowledge_source.py b/unconstrained-initialization/knowledge_sources/abstract_knowledge_source.py new file mode 100644 index 000000000..471faddef --- /dev/null +++ b/unconstrained-initialization/knowledge_sources/abstract_knowledge_source.py @@ -0,0 +1,47 @@ + +import abc + +class AbstractKnowledgeSource(object): + """Abstract class/interface to ensure that all KnowledgeSource classes have the same methods to be invoked + + CAUTION : The user does not need to make any changes in this file. + + Attributes + ---------- + balckboard (Blackboard): blackboard containing data and the knowledge sources + + Raises + ------ + NotImplementedError: error raised if the Verify method has not been implemented in the subclass + NotImplementedError: error raised if the Process method has not been implemented in the subclass + """ + __metaclass__ = abc.ABCMeta + + def __init__(self, blackboard): + self.blackboard = blackboard + + + @abc.abstractmethod + def verify(self) -> bool: + """Verify that the blackboard has all the data needed to run this knowledge source + + Returns + ------ + bool : True if the knowledge source can be applied, else False + + Raises + ------ + NotImplementedError: error raised if the Verify method has not been implemented in the subclass + Any Other Exception: to descripbe why the knowledge source can't run + """ + raise NotImplementedError('Must provide implementation in subclass.') + + @abc.abstractmethod + def process(self) -> None: + """Run the process the method on the blackboard + + Raises + ------ + NotImplementedError: error raised if the Process method has not been implemented in the subclass + """ + raise NotImplementedError('Must provide implementation in subclass.') \ No newline at end of file diff --git a/unconstrained-initialization/knowledge_sources/check_resolution.py b/unconstrained-initialization/knowledge_sources/check_resolution.py new file mode 100644 index 000000000..b09bffd45 --- /dev/null +++ b/unconstrained-initialization/knowledge_sources/check_resolution.py @@ -0,0 +1,94 @@ +#base imports +from knowledge_sources.abstract_knowledge_source import AbstractKnowledgeSource +import logging as log +log = log.getLogger(__file__) + +#KS imports +import numpy +from marshmallow import fields, validate +from schema import Schema, And, Use, Optional, SchemaError, Or + +class CheckResolution(AbstractKnowledgeSource): + """ + Create all services attributes from problem + """ + + def verify(self): + + problem = self.blackboard.problem + + if self.blackboard.problem is None: + raise AttributeError("Problem is None, not possible to check if the possible") + + if not isinstance(self.blackboard.problem, dict): + raise AttributeError("Problem is not of type dict, not possible to create the dictionnary") + + if "relations" in problem : + for relation in problem["relations"]: + if relation["type"] in ["order", "sequence"]: + raise NotImplementedError("This algorithm can't handle with sequence or order relations") + if relation["type"] in ["never_first", "never_last", "always_first", "always_last"]: + raise NotImplementedError("Can't handle positions for services") + + + for vehicle in problem["vehicles"]: + if "shiftPreference" in vehicle: + if vehicle["shiftPreference"] in ["force_start", "force_end"]: + raise NotImplementedError("This algorithm can't handle with vehicles shift_preferences for now") + + for service in problem["services"]: + if "activity" in service: + if "position" in service["activity"]: + if service["activity"]["position"] in ["always_first", "always_last", "never_first"]: + raise NotImplementedError("This algorithm can't handle with services positions for now") + elif "activities" in service: + for activity in service["activities"]: + if activity["positions"] in ["always_first", "always_last", "never_first"]: + raise NotImplementedError("This algorithm can't handle with services positions for now") + + + + + + problem_schema = Schema( + { + "matrices" : [{ + "time":[Or(float, int)], + "distance":[Or(float, int)] + }], + "vehicles" : [{ + 'endIndex': int, + 'startIndex': int, + 'matrixIndex': int + }], + "services" : + [ + { + 'matrixIndex':int, + 'id' : str, + Optional('priority'):int, + Optional("activity") :{ + 'timeWindows':Or([{ + "start": Or(int,float), + "end": Or(int,float), + "maximumLateness": Or(int, float) + }],[])}, + Optional("activities"): + [{ + 'timeWindows':Or([{ + "start": Or(int,float), + "end": Or(int,float), + "maximumLateness": Or(int, float) + }],[]) + }] + } + ] + }, + ignore_extra_keys=True + ) + problem_schema.validate(self.blackboard.problem) + + return True + + def process(self): + return None diff --git a/unconstrained-initialization/knowledge_sources/create_dictionnary_index_to_id.py b/unconstrained-initialization/knowledge_sources/create_dictionnary_index_to_id.py new file mode 100644 index 000000000..951f5e9fe --- /dev/null +++ b/unconstrained-initialization/knowledge_sources/create_dictionnary_index_to_id.py @@ -0,0 +1,61 @@ +#base imports +from knowledge_sources.abstract_knowledge_source import AbstractKnowledgeSource +import logging as log +log = log.getLogger(__file__) + +#KS imports +import numpy + +class CreateDictionnaryIndexId(AbstractKnowledgeSource): + """ + Create all services attributes from problem + """ + + def verify(self): + + if self.blackboard.problem is None: + raise AttributeError("Problem is None, not possible to create the dictionnary") + if not isinstance(self.blackboard.problem, dict): + raise AttributeError("Problem is not of type dict, not possible to create the dictionnary") + + for service in self.blackboard.problem["services"]: + if not 'id' in service: + raise AttributeError("At least one service has no Id, not possible to create the dictionnary") + + return True + + def process(self): + + problem = self.blackboard.problem + + # Services attributes + self.blackboard.service_index_to_id = {} + for service_index, service in enumerate(problem['services']): + self.blackboard.service_index_to_id[service_index] = service["id"] + + # Services attributes + self.blackboard.service_index_to_id = {} + self.blackboard.service_id_to_index_in_problem = {} + total_visit_number = 0 + + for service_index, service in enumerate(problem['services']): + visits_number = 1 + id = service["id"] + if "visitsNumber" in service : + visits_number = service["visitsNumber"] + for visit in range(visits_number): + if visits_number > 1 : + self.blackboard.service_id_to_index_in_problem[id] = service_index + self.blackboard.service_index_to_id[total_visit_number] = f"{id}_{visit}" + total_visit_number += 1 + else : + self.blackboard.service_id_to_index_in_problem[id] = service_index + self.blackboard.service_index_to_id[total_visit_number] = f"{id}" + total_visit_number += 1 + + for rest_index, rest in enumerate(self.blackboard.rests): + self.blackboard.service_index_to_id[total_visit_number + rest_index] = rest[0].get("id", f"rest_{rest_index}") + # Services attributes + self.blackboard.service_index_in_paths_to_pb_index = {} + total_visit_number = 0 + num_depot = 0 diff --git a/unconstrained-initialization/knowledge_sources/create_matrices_from_problem.py b/unconstrained-initialization/knowledge_sources/create_matrices_from_problem.py new file mode 100644 index 000000000..71947aa6f --- /dev/null +++ b/unconstrained-initialization/knowledge_sources/create_matrices_from_problem.py @@ -0,0 +1,71 @@ +#base imports +from knowledge_sources.abstract_knowledge_source import AbstractKnowledgeSource +import logging as log +from pathlib import Path +log = log.getLogger(Path(__file__).stem) + +#KS imports +import math +import numpy as np +from schema import Use, Const, And, Schema, Or + +class CreateMatricesFromProblem(AbstractKnowledgeSource): + """ + Create Matrices from problem + """ + + def verify(self): + + if self.blackboard.problem is None: + raise AttributeError("Problem is None, not possible to create matrices") + if not isinstance(self.blackboard.problem, dict): + raise AttributeError("Problem is not of type dict, not possible to create matrices") + + + problem_schema = Schema( + { + "matrices" : [{ + "time":[Or(float, int)], + "distance":[Or(float, int)] + }], + "vehicles" : [{ + 'endIndex': int, + 'startIndex': int, + }], + "services" : [ + { + 'matrixIndex':int, + } + ] + }, + ignore_extra_keys=True + ) + problem_schema.validate(self.blackboard.problem) + + return True + + def process(self): + + matrices = self.blackboard.problem['matrices'] + num_matrices = len(matrices) + matrix_size = int(math.sqrt(len(matrices[0]['time']))) + + # Create empty 3D arrays for time_matrices and distance_matrices + time_matrices = np.zeros((num_matrices, matrix_size, matrix_size), dtype=np.float64) + distance_matrices = np.zeros((num_matrices, matrix_size, matrix_size), dtype=np.float64) + + for matrix_index, matrix in enumerate(matrices): + + # Create and fill time_matrix + for pointFrom in range(matrix_size): + for pointTo in range(matrix_size): + time_matrices[matrix_index, pointFrom, pointTo] = matrix["time"][pointFrom * matrix_size + pointTo] + + # Create and fill distance_matrix + for pointFrom in range(matrix_size): + for pointTo in range(matrix_size): + distance_matrices[matrix_index, pointFrom, pointTo] = matrix['distance'][pointFrom * matrix_size + pointTo] + + # Matrices + self.blackboard.distance_matrices = distance_matrices + self.blackboard.time_matrices = time_matrices diff --git a/unconstrained-initialization/knowledge_sources/create_services_attributes_from_problem.py b/unconstrained-initialization/knowledge_sources/create_services_attributes_from_problem.py new file mode 100644 index 000000000..9212393f6 --- /dev/null +++ b/unconstrained-initialization/knowledge_sources/create_services_attributes_from_problem.py @@ -0,0 +1,121 @@ +#base imports +from knowledge_sources.abstract_knowledge_source import AbstractKnowledgeSource +import logging as log +from pathlib import Path +log = log.getLogger(Path(__file__).stem) + +#KS imports +import numpy + +def list_all_rests(problem): + rests = [] + for vehicle_index, vehicle in enumerate(problem["vehicles"]): + vehicle_rests = vehicle.get("rests",[]) + for rest in vehicle_rests: + rests.append((rest, vehicle_index )) + return rests + + + +class CreateServicesAttributesFromProblem(AbstractKnowledgeSource): + """ + Create all services attributes from problem + """ + + def verify(self): + + if self.blackboard.problem is None: + raise AttributeError("Problem is None, not possible to create services attributes") + if not isinstance(self.blackboard.problem, dict): + raise AttributeError("Problem is not of type dict, not possible to create services attributes") + + for service in self.blackboard.problem["services"]: + if not all(key in service for key in ("matrixIndex", "timeWindows", "id")): + raise AttributeError("API did not provide any TW for at least one service") + + return True + + def process(self): + + problem = self.blackboard.problem + + # num_units = max(len(problem['vehicles'][0]['capacities']),1) + num_units = max(max(len(vehicle.get("capacities",[])) for vehicle in problem["vehicles"]),1) + + num_depots = len(list(set([vehicle['startIndex'] for vehicle in problem['vehicles']]))) + + num_services = len(problem["services"]) + sum(len(vehicle.get("rests",[])) for vehicle in problem['vehicles'] ) + + rests = list_all_rests(problem) + + num_TW = max(max(len(service.get('timeWindows',[])) for service in problem['services']),1) + + # Services attributes + services_TW_starts = numpy.full((num_services, num_TW), 0, dtype=numpy.float64) + services_TW_ends = numpy.full((num_services, num_TW), 0, dtype=numpy.float64) + services_max_lateness = numpy.full((num_services, num_TW), 0, dtype=numpy.float64) + services_duration = numpy.full(num_services,0,dtype=numpy.float64) + services_setup_duration = numpy.full(num_services,0,dtype=numpy.float64) + services_quantities = numpy.full((num_services,num_units),0,dtype=numpy.float64) + services_matrix_index = numpy.full(num_services, -1,dtype=numpy.int32) + services_sticky_vehicles = {} + is_break = numpy.full(num_services,0,dtype=numpy.int32) + + for service_index, service in enumerate(problem['services']): + services_sticky_vehicles[service_index] = numpy.array(service.get("vehicleIndices", []), dtype=numpy.int32) + services_matrix_index[service_index] = (service.get("matrixIndex", 0)) + timeWindows = service.get('timeWindows', []) + if len(timeWindows) > 0: + for tw_index, tw in enumerate(timeWindows) : + services_TW_starts[service_index, tw_index] = tw.get('start',0) + if service.get('lateMultiplier', 0) > 0: + maxLateness = tw.get("maximumLateness", 0) + else : + maxLateness = 0 + services_max_lateness[service_index, tw_index] = maxLateness + services_TW_ends[service_index, tw_index] = tw.get('end',-1) + else : + services_TW_starts[service_index,0] = 0 + services_TW_ends[service_index,0] = -1 + services_duration[service_index] = (service.get('duration', 0)) + services_setup_duration[service_index] = (service.get('setupDuration',0)) + quantities = service.get('quantities',[]) + if len(quantities) > 0 : + for unit_index, quantity in enumerate(quantities): + services_quantities[service_index, unit_index] = quantity + else : + services_quantities[service_index, 0] = 0 + + num_services_without_rests = len(problem['services']) + + + for rest_index, rest in enumerate(rests): + is_break[num_services_without_rests + rest_index] = 1 + services_matrix_index[num_services_without_rests + rest_index] = -1 + tw = rest[0].get('timeWindow', []) + services_TW_starts[num_services_without_rests + rest_index, tw_index] = tw.get('start',0) + services_max_lateness[num_services_without_rests + rest_index, tw_index] = 0 + services_TW_ends[num_services_without_rests + rest_index, tw_index] = tw.get('end',-1) + services_duration[num_services_without_rests + rest_index] = rest[0].get("duration", 0) + services_setup_duration[num_services_without_rests + rest_index] = rest[0].get("setupDuration", 0) + services_sticky_vehicles[num_services_without_rests + rest_index] = numpy.array([rest[1]], dtype=numpy.int32) + + for unit_index in range(num_units): + services_quantities[num_services_without_rests + rest_index, unit_index] = 0 + + + + # Services attributes + self.blackboard.service_matrix_index = services_matrix_index + self.blackboard.start_tw = services_TW_starts + self.blackboard.end_tw = services_TW_ends + self.blackboard.services_max_lateness = services_max_lateness + self.blackboard.durations = services_duration + self.blackboard.setup_durations = services_setup_duration + self.blackboard.services_volumes = services_quantities + self.blackboard.size = num_services + self.blackboard.num_units = num_units + self.blackboard.num_services = num_services + self.blackboard.service_sticky_vehicles = services_sticky_vehicles + self.blackboard.is_break = is_break + self.blackboard.rests = rests diff --git a/unconstrained-initialization/knowledge_sources/create_vehicles_attributes_from_problem.py b/unconstrained-initialization/knowledge_sources/create_vehicles_attributes_from_problem.py new file mode 100644 index 000000000..3b7d9c6ef --- /dev/null +++ b/unconstrained-initialization/knowledge_sources/create_vehicles_attributes_from_problem.py @@ -0,0 +1,139 @@ +#base imports +from knowledge_sources.abstract_knowledge_source import AbstractKnowledgeSource +import logging as log +from pathlib import Path +log = log.getLogger(Path(__file__).stem) + +#KS imports +import numpy + +class CreateVehiclesAttributesFromProblem(AbstractKnowledgeSource): + """ + Create all vehicles attributes from problem + """ + + def verify(self): + + problem = self.blackboard.problem + if problem is None: + raise AttributeError("Problem is None, not possible to create vehicles attributes") + if not isinstance(problem, dict): + raise AttributeError("Problem is not of type dict, not possible to create vehicles attributes") + + if not "vehicles" in problem: + raise AttributeError("There is no vehicle in the problem, not possible to run") + + for vehicle in problem["vehicles"]: + if not "id" in vehicle: + raise AttributeError("At least one vehicle doesn't have an Id, not possible to create vehicle attributes") + + return True + + def process(self): + + problem = self.blackboard.problem + # Vehicle attributes + self.blackboard.num_vehicle = len(problem['vehicles']) + num_services = len(problem['services']) + cost_distance_multiplier = [] + cost_time_multiplier = [] + vehicles_capacities = [[] for i in range (self.blackboard.num_vehicle) ] + vehicles_TW_starts = [] + vehicles_TW_ends = [] + vehicles_TW_margin = [] + vehicles_distance_max = [] + vehicles_duration_max = [] + vehicles_fixed_costs = [] + vehicles_overload_multiplier = [[] for i in range (self.blackboard.num_vehicle) ] + vehicle_matrix_index = [] + vehicle_start_index = [] + vehicle_end_index = [] + force_start = [] + free_approach = [] + free_return = [] + vehicle_id_index = {} + previous_vehicle = problem['vehicles'][0] + + for vehicle_index,vehicle in enumerate(problem['vehicles']) : + vehicle_id_index[vehicle['id']] = vehicle_index + vehicle_matrix_index.append(vehicle.get("matrixIndex",0)) + + vehicle_start_index.append(vehicle.get("startIndex", 0)) + + vehicle_end_index.append(vehicle.get("endIndex", 0)) + free_approach.append(vehicle.get("free_approach", 0)) + free_approach.append(vehicle.get("free_return", 0)) + shift_preference = vehicle.get("shiftPreference","") + if shift_preference == "force_start": + force_start.append(2) + else : + force_start.append(1) + + vehicles_fixed_costs.append(vehicle.get("costFixed", 0)) + if 'capacities' in vehicle: + if len(vehicle['capacities']) > 0: + for capacity in vehicle['capacities']: + vehicles_capacities[vehicle_index].append(capacity['limit']) + if "overloadMultiplier" in capacity: + vehicles_overload_multiplier[vehicle_index].append(capacity["overloadMultiplier"]) + else: + vehicles_overload_multiplier[vehicle_index].append(0) + else : + vehicles_capacities[vehicle_index].append(-1) + vehicles_overload_multiplier[vehicle_index].append(0) + cost_distance_multiplier.append(vehicle.get('costDistanceMultiplier',0)) + cost_time_multiplier.append(vehicle.get('costTimeMultiplier')) + vehicles_TW_starts.append(vehicle["timeWindow"].get("start",0)) + vehicles_TW_ends.append(vehicle["timeWindow"].get("end",-1)) + if vehicle.get("costLateMultiplier", 0) > 0: + vehicles_TW_margin.append(vehicle.get("maximumLateness", 0)) + else : + vehicles_TW_margin.append(0) + distance_max = vehicle.get("distance",0) + if distance_max==0: + vehicles_distance_max.append(-1) + else : + vehicles_distance_max.append(distance_max) + duration_max = vehicle.get("duration",0) + if duration_max == 0 : + vehicles_duration_max.append(-1) + else: + vehicles_duration_max.append(duration_max) + previous_vehicle = vehicle + if 'capacities' in problem['vehicles'][0] and len(problem['vehicles'][0]['capacities']) > 0: + self.blackboard.max_capacity = int(problem['vehicles'][0]['capacities'][0]['limit']) + else: + self.blackboard.max_capacity = 2**30 + + + # Vehicles attributes + self.blackboard.cost_time_multiplier = numpy.array(cost_time_multiplier, dtype=numpy.float64) + self.blackboard.cost_distance_multiplier = numpy.array(cost_distance_multiplier, dtype=numpy.float64) + self.blackboard.vehicle_capacities = numpy.array(vehicles_capacities, dtype=numpy.float64) + self.blackboard.vehicles_TW_starts = numpy.array(vehicles_TW_starts, dtype=numpy.float64) + self.blackboard.vehicles_TW_ends = numpy.array(vehicles_TW_ends, dtype=numpy.float64) + self.blackboard.vehicle_time_window_margin = numpy.array(vehicles_TW_margin, dtype=numpy.float64) + self.blackboard.vehicles_distance_max = numpy.array(vehicles_distance_max, dtype=numpy.float64) + self.blackboard.vehicles_duration_max = numpy.array(vehicles_duration_max, dtype=numpy.float64) + self.blackboard.vehicles_fixed_costs = numpy.array(vehicles_fixed_costs, dtype=numpy.float64) + self.blackboard.vehicles_overload_multiplier = numpy.array(vehicles_overload_multiplier, dtype=numpy.float64) + self.blackboard.vehicles_matrix_index = numpy.array(vehicle_matrix_index, dtype=numpy.int32) + self.blackboard.force_start = numpy.array(force_start, dtype=numpy.int32) + self.blackboard.free_approach = numpy.array(free_approach, dtype=numpy.int32) + self.blackboard.free_return = numpy.array(free_return, dtype=numpy.int32) + + self.blackboard.vehicle_end_index = numpy.array(vehicle_end_index, dtype=numpy.int32) + self.blackboard.vehicle_start_index = numpy.array(vehicle_start_index, dtype=numpy.int32) + self.blackboard.previous_vehicle = numpy.array([ -1 for _ in range(self.blackboard.num_vehicle)], dtype= numpy.int32) + + self.blackboard.vehicle_id_index = vehicle_id_index + + if "relations" in problem : + for relation in problem['relations']: + if relation['type'] == "vehicle_trips": + size = len(relation['linkedVehicleIds']) + for i in range(size): + if i == 0 : + self.blackboard.previous_vehicle[vehicle_id_index[relation['linkedVehicleIds'][i]]] = -1 + else : + self.blackboard.previous_vehicle[vehicle_id_index[relation['linkedVehicleIds'][i]]] = vehicle_id_index[relation['linkedVehicleIds'][i-1]] diff --git a/unconstrained-initialization/knowledge_sources/deserialize_problem.py b/unconstrained-initialization/knowledge_sources/deserialize_problem.py new file mode 100644 index 000000000..517ddd04c --- /dev/null +++ b/unconstrained-initialization/knowledge_sources/deserialize_problem.py @@ -0,0 +1,31 @@ +#base imports +from knowledge_sources.abstract_knowledge_source import AbstractKnowledgeSource +import logging as log +from pathlib import Path +log = log.getLogger(Path(__file__).stem) + +#KS imports +from google.protobuf.json_format import MessageToDict +import localsearch_vrp_pb2 + +class DeserializeProblem(AbstractKnowledgeSource): + """ + Deserialization of the problem dictionnary + """ + + def verify(self): + + if self.blackboard.instance is None: + raise AttributeError("Instance is None, not possible to create the problem") + + return True + + def process(self): + instance = self.blackboard.instance + + with open(instance, 'rb') as f: + self.blackboard.problem = localsearch_vrp_pb2.Problem() + self.blackboard.problem.ParseFromString(f.read()) + self.blackboard.problem = MessageToDict(self.blackboard.problem, including_default_value_fields=True) + + diff --git a/unconstrained-initialization/knowledge_sources/get_arguments.py b/unconstrained-initialization/knowledge_sources/get_arguments.py new file mode 100644 index 000000000..d2d010d6f --- /dev/null +++ b/unconstrained-initialization/knowledge_sources/get_arguments.py @@ -0,0 +1,58 @@ +#base imports +from knowledge_sources.abstract_knowledge_source import AbstractKnowledgeSource +import logging as log +from pathlib import Path +log = log.getLogger(Path(__file__).stem) + +#KS imports +import sys +from os import path + +class GetArguments(AbstractKnowledgeSource): + """ + Get input file (instance of the problem) and output file of the algorithm + """ + + def verify(self): + + # Check arguments exists + needed_arguments = [ + "-time_limit_in_ms", + "-instance_file", + "-solution_file", + ] + args = sys.argv[1:] + + for needed_argument in needed_arguments: + if needed_argument not in args: + raise AttributeError(f"Input argument '{needed_argument}' not specified") + + #Check time_limit is numerical value + index = args.index("-time_limit_in_ms") + time_limit = args[index + 1] + if not time_limit.isnumeric(): + raise ValueError(f"Input argument 'time_limit_in_ms' should be numeric (got {time_limit})") + + #Check instance file exists + index = args.index("-instance_file") + instance = args[index + 1] + if not path.exists(instance): + raise FileNotFoundError(f"Instance file ({instance}) doesn't not exist : can't build problem") + + return True + + + def process(self): + #Get arguments + args = sys.argv[1:] + + #Get time limit + index = args.index("-time_limit_in_ms") + self.blackboard.time_limit = int(args[index + 1]) / 1000 + + index = args.index("-instance_file") + self.blackboard.instance = args[index + 1] + + index = args.index("-solution_file") + self.blackboard.output_file = args[index + 1] + print("output_file : ", self.blackboard.output_file) diff --git a/unconstrained-initialization/knowledge_sources/optimize_solution.py b/unconstrained-initialization/knowledge_sources/optimize_solution.py new file mode 100644 index 000000000..206d37590 --- /dev/null +++ b/unconstrained-initialization/knowledge_sources/optimize_solution.py @@ -0,0 +1,39 @@ +#base imports +from knowledge_sources.abstract_knowledge_source import AbstractKnowledgeSource +import logging as log +from pathlib import Path +log = log.getLogger(Path(__file__).stem) + +#KS imports +from fastvrpy import solver + +class OptimizeSolution(AbstractKnowledgeSource): + """ + Create all vehicles attributes from problem + """ + + def verify(self): + + if self.blackboard.solution is None: + raise AttributeError("No solution specified... can't optimize nothing !") + + if self.blackboard.time_limit is None: + raise AttributeError("No time limit for optimization") + + if self.blackboard.problem is None: + raise AttributeError("No problem (it's a problem) must be specified to optimize") + + if self.blackboard.max_capacity is None: + raise AttributeError("No max_capacity, must be specified to optimize") + + return True + + def process(self): + + solver.optimize( + solution = self.blackboard.solution, + max_execution_time=int(self.blackboard.time_limit), + problem=self.blackboard.problem, + groups_max_capacity = self.blackboard.max_capacity, + grouping = False + ) diff --git a/unconstrained-initialization/knowledge_sources/parse_and_serialize_solution.py b/unconstrained-initialization/knowledge_sources/parse_and_serialize_solution.py new file mode 100644 index 000000000..b18384bd5 --- /dev/null +++ b/unconstrained-initialization/knowledge_sources/parse_and_serialize_solution.py @@ -0,0 +1,82 @@ +#base imports +from knowledge_sources.abstract_knowledge_source import AbstractKnowledgeSource +import logging as log +from pathlib import Path +from google.protobuf import text_format +log = log.getLogger(Path(__file__).stem) + +#KS imports +import numpy +from sklearn.cluster import KMeans, AgglomerativeClustering, OPTICS, SpectralClustering, MiniBatchKMeans, DBSCAN +from fastvrpy.core.solutions import cvrptw + +from google.protobuf.json_format import MessageToDict +import localsearch_result_pb2 +import json + + + +class ParseAndSerializeSolution(AbstractKnowledgeSource): + """ + Parse and Serialize the founded solution + """ + def verify(self): + + if self.blackboard.solution is None: + raise AttributeError("Solution is None, not possible to parse and serialize the solution") + + if self.blackboard.output_file is None: + raise AttributeError("Output file is None, not possible to parse and serialize the solution") + + + return True + + def process(self): + solution = self.blackboard.solution + paths = numpy.asarray(solution.paths) + result = localsearch_result_pb2.Result() + for path_index,path in enumerate(paths): + if len(set(path)) > 1 : + route = result.routes.add() + store = route.activities.add() + store.id = "store" + store.index = -1 + store.start_time = int(self.blackboard.solution.vehicle_starts[path_index]) + store.type = "start" + for stop_index,stop in enumerate(path): + if stop != -1 and solution.is_break[stop] == 0: + activity = route.activities.add() + activity.id = self.blackboard.service_index_to_id[stop] + activity.index = stop + activity.start_time = int(self.blackboard.solution.starts[path_index, stop_index+1]) + activity.type = "service" + elif stop != -1 and solution.is_break[stop] == 1: + rest = route.activities.add() + rest.type = "break" + rest.id = self.blackboard.service_index_to_id[stop] + rest.start_time = int(self.blackboard.solution.starts[path_index, stop_index+1]) + else: + break + store_return = route.activities.add() + store_return.id = "store" + store_return.index = -1 + store_return.start_time = int(self.blackboard.solution.vehicle_ends[path_index]) + store_return.type = "end" + cost_details = route.cost_details + cost_details.fixed = solution.vehicle_fixed_costs[path_index] + cost_details.distance = solution.distances[path_index] * solution.cost_distance_multiplier[path_index] + cost_details.time = solution.travel_times[path_index] * solution.cost_time_multiplier[path_index] + + else: + route = result.routes.add() + start_route = route.activities.add() + start_route.type ="start" + start_route.index = -1 + start_route.start_time = 0 + end_route = route.activities.add() + end_route.type ="start" + end_route.index = -1 + end_route.start_time = 0 + f = open(self.blackboard.output_file, "wb") + f.write(result.SerializeToString()) + f.close() diff --git a/unconstrained-initialization/knowledge_sources/print_kpis.py b/unconstrained-initialization/knowledge_sources/print_kpis.py new file mode 100644 index 000000000..946efa3a1 --- /dev/null +++ b/unconstrained-initialization/knowledge_sources/print_kpis.py @@ -0,0 +1,96 @@ +#base imports +from knowledge_sources.abstract_knowledge_source import AbstractKnowledgeSource +import logging as log +from pathlib import Path +log = log.getLogger(Path(__file__).stem) + +#KS imports +import numpy + +def tw_select(solution, service, start): + tw_index_selected = 0 + for tw_index,tw in enumerate(solution.start_time_windows[service]): + if (solution.end_time_windows[service][tw_index] >= start) and (solution.start_time_windows[service][tw_index] <= start): + tw_index_selected = tw_index + break + return tw_index_selected + +def service_is_late(solution, service, start): + tw_index_selected = tw_select(solution, service, start) + return start > solution.end_time_windows[service][tw_index_selected] + +def service_is_early(solution, service, start): + check_if_service_is_early = lambda tw_start : tw_start > start + early = all(check_if_service_is_early(tw_start) for tw_start in solution.start_time_windows[service]) + return early + +class PrintKpis(AbstractKnowledgeSource): + """ + Create all services attributes from problem + """ + + def verify(self): + + if self.blackboard.solution is None: + raise AttributeError("No solution... Can't print KPIS of nothing") + + return True + + def process(self): + solution = self.blackboard.solution + early = 0 + lates = 0 + cumul = 0 + max_late = 0 + overloads = [0 for i in range(solution.num_units)] + vehicle_late = 0 + vehicle_late_time = 0 + vehicle_over_distance = 0 + total_travel_distance = 0 + total_travel_time = 0 + + for vehicle in range(solution.num_vehicles): + total_travel_distance += solution.distances[vehicle] + total_travel_time += solution.travel_times[vehicle] + if solution.vehicle_max_distance[vehicle] > -1 and solution.distances[vehicle] > solution.vehicle_max_distance[vehicle] : + vehicle_over_distance += 1 + for unit_index in range(solution.num_units): + if solution.vehicle_capacities[vehicle,unit_index] > -1 and solution.vehicle_occupancies[vehicle, unit_index] > solution.vehicle_capacities[vehicle, unit_index]: + overloads[unit_index] += 1 + if solution.vehicle_end_time_window[vehicle] > -1 and solution.vehicle_ends[vehicle] > solution.vehicle_end_time_window[vehicle]: + vehicle_late += 1 + vehicle_late_time += solution.vehicle_ends[vehicle] - solution.vehicle_end_time_window[vehicle] + + for point in range(solution.vehicle_num_services[vehicle]): + start = solution.starts[vehicle][point+1] + service = solution.paths[vehicle][point] + + + s_tw = solution.start_time_windows[service,0] + e_tw = solution.max_end_time_windows[service] + + if start < s_tw : + early += 1 + cumul += s_tw - start + elif e_tw > -1 and start > e_tw: + #print(start, s_tw, e_tw, vehicle, point) + lates += 1 + cumul += start - e_tw + if start - e_tw > max_late: + max_late = start - e_tw + + vehicle_mean_late = vehicle_late_time/60/vehicle_late if vehicle_late > 0 else 0 + + log.info(f"Print KPIs") + log.info(f"SOLUTION COST {solution.total_cost}" ) + log.info(f"Travel distance : {total_travel_distance/1000} kilometers" ) + log.info(f"Travel time : {total_travel_time/3600} hours" ) + log.debug(f"Partial costs \n {numpy.array(solution.costs)}") + log.debug(f"Number services \n {numpy.array(solution.vehicle_num_services)}") + log.info(f"Vehicle distance violations {vehicle_over_distance}") + log.info(f"Vehicle overloads {overloads}") + log.info(f"{vehicle_late} vehicles with working hours overflow (mean {vehicle_mean_late} minutes)" ) + log.info(f"Too early (MUST be 0 by construction) {early}") + log.info(f"Too lates {lates}" ) + log.info(f"Total late {cumul}") + log.info(f"Worse late {max_late/60}") diff --git a/unconstrained-initialization/knowledge_sources/process_clustering_initial_paths.py b/unconstrained-initialization/knowledge_sources/process_clustering_initial_paths.py new file mode 100644 index 000000000..bd1217c98 --- /dev/null +++ b/unconstrained-initialization/knowledge_sources/process_clustering_initial_paths.py @@ -0,0 +1,100 @@ +#base imports +from knowledge_sources.abstract_knowledge_source import AbstractKnowledgeSource +import logging as log +from pathlib import Path +log = log.getLogger(Path(__file__).stem) + +#KS imports +from sklearn.cluster import AgglomerativeClustering +import numpy + +class ProcessClusteringInitialPaths(AbstractKnowledgeSource): + """ + Create all vehicles attributes from problem + """ + + def verify(self): + + if self.blackboard.num_vehicle is None: + raise AttributeError("num_vehicle not specified can't initilaize paths") + + if self.blackboard.time_matrices is None: + raise AttributeError("No distance matrix can't initialize paths") + + if self.blackboard.time_matrices[0].shape[0] != self.blackboard.time_matrices[0].shape[1]: + raise AttributeError("time_matrices must be a square matrices") + + return True + + def process(self): + + log.info("Process Initial Solution") + log.debug("-- Clustering") + num_vehicle = self.blackboard.num_vehicle + # recalculate matrix for AgglomerativeClustering + # TODO : use custom metric 'precomputed' + + matrix = [] + + time_matrix = self.blackboard.problem["matrices"][0]["time"] + matrix_size = self.blackboard.problem["matrices"][0]["size"] + problem = self.blackboard.problem + + routes = problem.get("routes", []) + + log.info(f" routes : {routes}") + log.info(f"vehicle_id_index {self.blackboard.vehicle_id_index}") + + if len(routes) > 0: + service_id_to_index = {value: key for key, value in self.blackboard.service_index_to_id.items()} + self.blackboard.unassigned_services = numpy.full(self.blackboard.num_services + 1, -1, dtype=numpy.int32) + self.blackboard.paths = numpy.full((num_vehicle, self.blackboard.num_services + 1), -1, dtype=numpy.int32) + for route in routes: + vehicle_id = route.get("vehicleId") + vehicle_index = self.blackboard.vehicle_id_index[vehicle_id] + for service_index_in_route, service_id in enumerate(route.get("serviceIds")): + service_index = service_id_to_index[service_id] + self.blackboard.paths[vehicle_index, service_index_in_route] = service_index + for service_index in range (self.blackboard.num_services): + if not numpy.any(numpy.isin(self.blackboard.paths, service_index)): + self.blackboard.unassigned_services[service_index] = service_index + mask = self.blackboard.unassigned_services == -1 + + self.blackboard.unassigned_services = numpy.concatenate((self.blackboard.unassigned_services[~mask], self.blackboard.unassigned_services[mask])) + + + else : + self.blackboard.unassigned_services = numpy.full(self.blackboard.num_services + 1, -1, dtype=numpy.int32) + for serviceFrom in problem["services"]: + matrix_row = [] + for serviceTo in problem["services"]: + matrix_row.append(time_matrix[serviceFrom["matrixIndex"] * matrix_size + serviceTo["matrixIndex"]]) + matrix.append(matrix_row) + + matrix = numpy.array(matrix) + + cluster = AgglomerativeClustering(n_clusters=min(num_vehicle, matrix.shape[0]), metric='precomputed', linkage='complete').fit(matrix) + log.debug("-- Compute initial solution") + num_services = numpy.zeros(num_vehicle, dtype=int) + for i in range(0, cluster.labels_.size): + vehicle = cluster.labels_[i] + num_services[vehicle] += 1 + + max_capacity = numpy.max(num_services) + 10 #Add margin to let algorithm the possibility to optimize something + num_services = numpy.zeros(num_vehicle, dtype=int) + self.blackboard.paths = numpy.full((num_vehicle, self.blackboard.num_services + 1), -1, dtype=numpy.int32) + for i in range(0, cluster.labels_.size): + vehicle = cluster.labels_[i] + position = num_services[vehicle] + self.blackboard.paths[vehicle][position] = i + num_services[vehicle] += 1 + + reverse_services_dict = {} + for index, service_id in self.blackboard.service_index_to_id.items(): + reverse_services_dict[service_id] = index + + for rest in self.blackboard.rests: + vehicle = rest[1] + index = reverse_services_dict[rest[0].get("id")] + self.blackboard.paths[vehicle][num_services[vehicle]] = index + num_services[vehicle] += 1 diff --git a/unconstrained-initialization/knowledge_sources/process_initial_solution.py b/unconstrained-initialization/knowledge_sources/process_initial_solution.py new file mode 100644 index 000000000..3b61848ea --- /dev/null +++ b/unconstrained-initialization/knowledge_sources/process_initial_solution.py @@ -0,0 +1,158 @@ +#base imports +from knowledge_sources.abstract_knowledge_source import AbstractKnowledgeSource +import logging as log +from pathlib import Path +log = log.getLogger(Path(__file__).stem) + +#KS imports +import numpy +from sklearn.cluster import KMeans, AgglomerativeClustering, OPTICS, SpectralClustering, MiniBatchKMeans, DBSCAN +from fastvrpy.core.solutions import cvrptw + + + +class ProcessInitialSolution(AbstractKnowledgeSource): + """ + Process initial solution + """ + def verify(self): + + if self.blackboard.paths is None: + raise AttributeError("Paths are None, not possible to initialize a solution") + if not isinstance(self.blackboard.paths, numpy.ndarray): + raise AttributeError("Paths are not of type numpy.array, not possible to initialize a solution") + if self.blackboard.cost_time_multiplier is None: + raise AttributeError("Cost time multiplier vector is None, not possible to initialize a solution") + if not isinstance(self.blackboard.cost_time_multiplier, numpy.ndarray): + raise AttributeError("Cost time multplier vector is not of type numpy.array, not possible to initialize a solution") + if self.blackboard.cost_time_multiplier.shape[0] != self.blackboard.num_vehicle : + raise AttributeError("Lengh of cost time multiplier vector is not equal to the number of vehicles, not possible to initialize a solution") + + if self.blackboard.cost_distance_multiplier is None: + raise AttributeError("Cost distance multiplier vector is None, not possible to initialize a solution") + if not isinstance(self.blackboard.cost_distance_multiplier, numpy.ndarray): + raise AttributeError("Cost distance multiplier vector is not of type numpy.array, not possible to initialize a solution") + if self.blackboard.cost_distance_multiplier.shape[0] != self.blackboard.num_vehicle : + raise AttributeError("Lengh of cost distance multiplier vector is not equal to the number of vehicles, not possible to initialize a solution") + + if self.blackboard.vehicle_capacities is None: + raise AttributeError("Capacity vector is None, not possible to initialize a solution") + if not isinstance(self.blackboard.vehicle_capacities, numpy.ndarray): + raise AttributeError("Capacity vector is not of type numpy.array, not possible to initialize a solution") + if self.blackboard.vehicle_capacities.shape[0] != self.blackboard.num_vehicle : + raise AttributeError("Lengh of Capacity vector is not equal to the number of vehicles, not possible to initialize a solution") + + if self.blackboard.vehicles_TW_starts is None: + raise AttributeError("Vehicle TW_Starts vector is None, not possible to initialize a solution") + if not isinstance(self.blackboard.vehicles_TW_starts, numpy.ndarray): + raise AttributeError("Vehicle TW_Starts vector is not of type numpy.array, not possible to initialize a solution") + if self.blackboard.vehicles_TW_starts.shape[0] != self.blackboard.num_vehicle : + raise AttributeError("Lengh of Vehicle TW_Starts vector is not equal to the number of vehicles, not possible to initialize a solution") + + if self.blackboard.vehicles_TW_ends is None: + raise AttributeError("Vehicle TW_Ends vector is None, not possible to initialize a solution") + if not isinstance(self.blackboard.vehicles_TW_ends, numpy.ndarray): + raise AttributeError("Vehicle TW_Ends vector is not of type numpy.array, not possible to initialize a solution") + if self.blackboard.vehicles_TW_ends.shape[0] != self.blackboard.num_vehicle : + raise AttributeError("Lengh of Vehicle TW_Ends vector is not equal to the number of vehicles, not possible to initialize a solution") + + if self.blackboard.vehicles_distance_max is None: + raise AttributeError("vehicles_distance_max vector is None, not possible to initialize a solution") + if not isinstance(self.blackboard.vehicles_distance_max, numpy.ndarray): + raise AttributeError("vehicles_distance_max vector is not of type numpy.array, not possible to initialize a solution") + if self.blackboard.vehicles_distance_max.shape[0] != self.blackboard.num_vehicle : + raise AttributeError("Lengh of vehicles_distance_max vector is not equal to the number of vehicles, not possible to initialize a solution") + + if self.blackboard.vehicles_fixed_costs is None: + raise AttributeError("vehicles_fixed_costs vector is None, not possible to initialize a solution") + if not isinstance(self.blackboard.vehicles_fixed_costs, numpy.ndarray): + raise AttributeError("vehicles_fixed_costs vector is not of type numpy.array, not possible to initialize a solution") + if self.blackboard.vehicles_fixed_costs.shape[0] != self.blackboard.num_vehicle : + raise AttributeError("Lengh of vehicles_fixed_costs vector is not equal to the number of vehicles, not possible to initialize a solution") + + if self.blackboard.vehicles_overload_multiplier is None: + raise AttributeError("vehicles_overload_multiplier vector is None, not possible to initialize a solution") + if not isinstance(self.blackboard.vehicles_overload_multiplier, numpy.ndarray): + raise AttributeError("vehicles_overload_multiplier vector is not of type numpy.array, not possible to initialize a solution") + if self.blackboard.vehicles_overload_multiplier.shape[0] != self.blackboard.num_vehicle : + raise AttributeError("Lengh of vehicles_overload_multiplier vector is not equal to the number of vehicles, not possible to initialize a solution") + if self.blackboard.previous_vehicle is None: + raise AttributeError("previous_vehicle vector is None, not possible to initialize a solution") + if not isinstance(self.blackboard.previous_vehicle, numpy.ndarray): + raise AttributeError("previous_vehicle vector is not of type numpy.array, not possible to initialize a solution") + if self.blackboard.previous_vehicle.shape[0] != self.blackboard.num_vehicle : + raise AttributeError("Lengh of previous_vehicle vector is not equal to the number of vehicles, not possible to initialize a solution") + + if self.blackboard.start_tw is None: + raise AttributeError("start_tw vector is None, not possible to initialize a solution") + if not isinstance(self.blackboard.start_tw, numpy.ndarray): + raise AttributeError("start_tw vector is not of type numpy.array, not possible to initialize a solution") + if self.blackboard.start_tw.shape[0] != self.blackboard.num_services : + raise AttributeError("Lengh of start_tw vector is not equal to the number of services, not possible to initialize a solution") + + if self.blackboard.end_tw is None: + raise AttributeError("end_tw vector is None, not possible to initialize a solution") + if not isinstance(self.blackboard.end_tw, numpy.ndarray): + raise AttributeError("end_tw vector is not of type numpy.array, not possible to initialize a solution") + if self.blackboard.end_tw.shape[0] != self.blackboard.num_services : + raise AttributeError("Lengh of end_tw vector is not equal to the number of services, not possible to initialize a solution") + + if self.blackboard.durations is None: + raise AttributeError("durations vector is None, not possible to initialize a solution") + if not isinstance(self.blackboard.durations, numpy.ndarray): + raise AttributeError("durations vector is not of type numpy.array, not possible to initialize a solution") + if self.blackboard.durations.shape[0] != self.blackboard.num_services : + raise AttributeError("Lengh of durations vector is not equal to the number of services, not possible to initialize a solution") + + if self.blackboard.setup_durations is None: + raise AttributeError("setup_durations vector is None, not possible to initialize a solution") + if not isinstance(self.blackboard.setup_durations, numpy.ndarray): + raise AttributeError("setup_durations vector is not of type numpy.array, not possible to initialize a solution") + if self.blackboard.setup_durations.shape[0] != self.blackboard.num_services : + raise AttributeError("Lengh of setup_durations vector is not equal to the number of services, not possible to initialize a solution") + + if self.blackboard.services_volumes is None: + raise AttributeError("services_volume vector is None, not possible to initialize a solution") + if not isinstance(self.blackboard.services_volumes, numpy.ndarray): + raise AttributeError("services_volume vector is not of type numpy.array, not possible to initialize a solution") + if self.blackboard.services_volumes.shape[0] != self.blackboard.num_services : + raise AttributeError("Lengh of services_volume vector is not equal to the number of services, not possible to initialize a solution") + + return True + + def process(self): + self.blackboard.solution = cvrptw.CVRPTW( + paths = self.blackboard.paths, + distance_matrix = self.blackboard.distance_matrices, + time_matrix = self.blackboard.time_matrices, + num_services = self.blackboard.num_services, + start_time_windows = self.blackboard.start_tw, + end_time_windows = self.blackboard.end_tw, + time_windows_margin = self.blackboard.services_max_lateness, + durations = self.blackboard.durations, + setup_durations = self.blackboard.setup_durations, + services_volumes = self.blackboard.services_volumes, + service_matrix_index = self.blackboard.service_matrix_index, + cost_distance_multiplier = self.blackboard.cost_distance_multiplier, + cost_time_multiplier = self.blackboard.cost_time_multiplier, + vehicle_capacities = self.blackboard.vehicle_capacities, + previous_vehicle = self.blackboard.previous_vehicle, + vehicle_max_distance = self.blackboard.vehicles_distance_max, + vehicle_max_travel_time = self.blackboard.vehicles_duration_max, + vehicle_fixed_costs = self.blackboard.vehicles_fixed_costs, + vehicle_overload_multiplier = self.blackboard.vehicles_overload_multiplier, + vehicle_start_time_window = self.blackboard.vehicles_TW_starts, + vehicle_end_time_window = self.blackboard.vehicles_TW_ends, + vehicle_time_window_margin = self.blackboard.vehicle_time_window_margin, + vehicle_matrix_index = self.blackboard.vehicles_matrix_index, + vehicle_start_index = self.blackboard.vehicle_start_index, + vehicle_end_index = self.blackboard.vehicle_end_index, + vehicle_start_mode = self.blackboard.force_start, + free_approach = self.blackboard.free_approach, + free_return = self.blackboard.free_return, + unassigned_services = self.blackboard.unassigned_services, + predecessor_successor_gap = None, + is_break = self.blackboard.is_break, + sticky_vehicles = self.blackboard.service_sticky_vehicles, + num_units = self.blackboard.num_units + ) diff --git a/unconstrained-initialization/knowledge_sources/tests/__init__.py b/unconstrained-initialization/knowledge_sources/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/unconstrained-initialization/knowledge_sources/tests/check_resolution_test.py b/unconstrained-initialization/knowledge_sources/tests/check_resolution_test.py new file mode 100644 index 000000000..7c1512c51 --- /dev/null +++ b/unconstrained-initialization/knowledge_sources/tests/check_resolution_test.py @@ -0,0 +1,107 @@ +from unittest.mock import Mock, patch +import pytest +import copy +from schema import Schema, And, Use, Optional, SchemaError, Or +import pdb + +from knowledge_sources.check_resolution import CheckResolution + + +problem_missing_keys = { "services": [ + {'id': "service_1"}, + {'id': "service_2"}, + {'id': "service_3"}, + ], + "vehicles": [ + + ], + "matrices":[] + } + +problem = { "services": [ + { + 'id': "service_1", + 'matrixIndex': 0, + + }, + { + 'id': "service_2", + 'matrixIndex': 1, + + } + ], + "vehicles": [ + { + "matrixIndex": 0, + "endIndex": 0, + "startIndex": 0, + } + ], + "matrices": [ + { + "time": [0,1,1,0], + "distance": [0,1,1,0] + } + ] + } + +def test_order_relation(): + blackboard = Mock(problem = copy.deepcopy(problem)) + knowledge_source = CheckResolution(blackboard) + blackboard.problem["relations"] = [ + { + "type" : "order", + "linked_ids" : ["1","2"] + } + ] + + with pytest.raises(NotImplementedError): + knowledge_source.verify() + +def test_sequence_relation(): + blackboard = Mock(problem = copy.deepcopy(problem)) + knowledge_source = CheckResolution(blackboard) + blackboard.problem["relations"] = [ + { + "type" : "sequence", + "linked_ids" : ["1","2"] + } + ] + + with pytest.raises(NotImplementedError): + knowledge_source.verify() + +def test_always_last_position(): + blackboard = Mock(problem = copy.deepcopy(problem)) + knowledge_source = CheckResolution(blackboard) + activity = { + "position": "always_last" + } + blackboard.problem["services"][0]["activity"] = activity + + with pytest.raises(NotImplementedError): + knowledge_source.verify() + +def test_always_first_position(): + blackboard = Mock(problem = copy.deepcopy(problem)) + knowledge_source = CheckResolution(blackboard) + activity = { + "position": "always_first" + } + blackboard.problem["services"][0]["activity"] = activity + + with pytest.raises(NotImplementedError): + knowledge_source.verify() + +def test_missing_keys(): + blackboard = Mock(problem = problem_missing_keys) + knowledge_source = CheckResolution(blackboard) + + with pytest.raises(SchemaError): + knowledge_source.verify() + +def test_ok_for_resolution(): + blackboard = Mock(problem = problem) + knowledge_source = CheckResolution(blackboard) + + assert knowledge_source.verify() diff --git a/unconstrained-initialization/knowledge_sources/tests/create_dictionnary_index_to_id_test.py b/unconstrained-initialization/knowledge_sources/tests/create_dictionnary_index_to_id_test.py new file mode 100644 index 000000000..e32805342 --- /dev/null +++ b/unconstrained-initialization/knowledge_sources/tests/create_dictionnary_index_to_id_test.py @@ -0,0 +1,29 @@ +from unittest.mock import Mock, patch +import pytest +import copy + +from knowledge_sources.create_dictionnary_index_to_id import CreateDictionnaryIndexId + + +problem = { "services": [ + {'id': "service_1"}, + {'id': "service_2"}, + {'id': "service_3"}, + ] + } + +def test_verify_missing_id_on_service(): + blackboard = Mock(problem = copy.deepcopy(problem)) + del blackboard.problem["services"][1]['id'] + knowledge_source = CreateDictionnaryIndexId(blackboard) + + with pytest.raises(AttributeError): + knowledge_source.verify() + +def test_process(): + blackboard = Mock(problem = copy.deepcopy(problem)) + knowledge_source = CreateDictionnaryIndexId(blackboard) + + knowledge_source.process() + + assert blackboard.service_index_to_id == { 0 : "service_1", 1 : "service_2", 2 : "service_3"} diff --git a/unconstrained-initialization/knowledge_sources/tests/create_matrices_from_problem_test.py b/unconstrained-initialization/knowledge_sources/tests/create_matrices_from_problem_test.py new file mode 100644 index 000000000..30c803970 --- /dev/null +++ b/unconstrained-initialization/knowledge_sources/tests/create_matrices_from_problem_test.py @@ -0,0 +1,65 @@ +from unittest.mock import Mock, patch +import pytest + +from knowledge_sources.create_matrices_from_problem import CreateMatricesFromProblem +import schema +import numpy + +@pytest.mark.parametrize("problem", [None, "Coucou"]) +def test_verify_problem_error(problem): + blackboard = Mock(problem = problem) + knowledge_source = CreateMatricesFromProblem(blackboard) + + with pytest.raises(AttributeError): + knowledge_source.verify() + + +def test_verify_problem_missing_keys(): + blackboard = Mock(problem = {}) + knowledge_source = CreateMatricesFromProblem(blackboard) + + with pytest.raises(schema.SchemaError): + knowledge_source.verify() + + +problem = { + "matrices" : [{ + "time":[0,2,3, + 4,0,6, + 7,8,0], + "distance":[1,2,3, + 4,5,6, + 7,8,9] + }], + "vehicles" : [{ + 'endIndex': 0, + 'startIndex': 0, + }, + { + 'endIndex': 0, + 'startIndex': 0, + }], + "services" : [ + { + 'matrixIndex':1, + }, + { + 'matrixIndex':2, + } + ] +} + +def test_verify_problem_ok(): + blackboard = Mock(problem = problem) + knowledge_source = CreateMatricesFromProblem(blackboard) + + assert knowledge_source.verify() == True + + +def test_process(): + blackboard = Mock(problem = problem) + knowledge_source = CreateMatricesFromProblem(blackboard) + + knowledge_source.process() + # assert (blackboard.distance_matrices == numpy.array([[[5,3,1,1],[5,6,7],[8,9,10]]], dtype=numpy.float64)).all() + assert (blackboard.time_matrices == numpy.array([[[0, 6, 4], [8, 0, 7], [2, 3, 0]]], dtype=numpy.float64)).all() diff --git a/unconstrained-initialization/knowledge_sources/tests/create_services_attributes_from_problem_test.py b/unconstrained-initialization/knowledge_sources/tests/create_services_attributes_from_problem_test.py new file mode 100644 index 000000000..f54db79cd --- /dev/null +++ b/unconstrained-initialization/knowledge_sources/tests/create_services_attributes_from_problem_test.py @@ -0,0 +1,113 @@ +from unittest.mock import Mock, patch +import pytest +import numpy +import copy + +from knowledge_sources.create_services_attributes_from_problem import CreateServicesAttributesFromProblem + +problem = { "services": [ + {'duration': 20, + 'vehicleIndices': [0], + 'matrixIndex': 2, + 'id': '470d440f', + 'exclusionCost': -1.0, + 'pointId': '470d440f-047b-460b-8a9c-fa1efa8b1595', + 'timeWindows': [], + 'quantities': [], + 'setupDuration': 0, + 'lateMultiplier': 0.0, + 'setupQuantities': [], + 'additionalValue': 0, + 'refillQuantities': [], + 'problemIndex': 0, + 'alternativeIndex': 0}, + {'duration': 20, + 'vehicleIndices': [0], + 'matrixIndex': 2, + 'id': '470d440g', + 'exclusionCost': -1.0, + 'pointId': '470d440f-047b-460b-8a9c-fa1efa8b1595', + 'timeWindows': [], + 'quantities': [], + 'setupDuration': 0, + 'lateMultiplier': 0.0, + 'setupQuantities': [], + 'additionalValue': 0, + 'refillQuantities': [], + 'problemIndex': 0, + 'alternativeIndex': 0} + ], + "vehicles" : + [{"capacities": []}] + } + +def test_verify_missing_TW_on_service(): + blackboard = Mock(problem = copy.deepcopy(problem)) + del blackboard.problem["services"][1]['timeWindows'] + knowledge_source = CreateServicesAttributesFromProblem(blackboard) + + with pytest.raises(AttributeError): + knowledge_source.verify() + +def test_verify_missing_matrixIndex_on_service(): + blackboard = Mock(problem = copy.deepcopy(problem)) + del blackboard.problem["services"][1]['matrixIndex'] + knowledge_source = CreateServicesAttributesFromProblem(blackboard) + + with pytest.raises(AttributeError): + knowledge_source.verify() + +def test_verify_missing_id_on_service(): + blackboard = Mock(problem = copy.deepcopy(problem)) + del blackboard.problem["services"][1]['id'] + knowledge_source = CreateServicesAttributesFromProblem(blackboard) + + with pytest.raises(AttributeError): + knowledge_source.verify() + +def test_no_problem(): + blackboard = Mock(problem = problem) + + knowledge_source = CreateServicesAttributesFromProblem(blackboard) + + assert knowledge_source.verify() == True + +def test_size_of_arrays(): + blackboard = Mock(problem = problem) + knowledge_source = CreateServicesAttributesFromProblem(blackboard) + knowledge_source.process() + + assert blackboard.size == 2 + assert blackboard.start_tw.size == blackboard.size + assert blackboard.end_tw.size == blackboard.size + assert blackboard.durations.size == blackboard.size + assert blackboard.setup_durations.size == blackboard.size + assert blackboard.services_volumes.size == blackboard.size + +def test_durations_array(): + blackboard = Mock(problem = problem) + knowledge_source = CreateServicesAttributesFromProblem(blackboard) + knowledge_source.process() + + assert (blackboard.durations == numpy.array([ 20., 20.])).all() + +def test_setup_durations_array(): + blackboard = Mock(problem = problem) + knowledge_source = CreateServicesAttributesFromProblem(blackboard) + knowledge_source.process() + + assert (blackboard.setup_durations == numpy.array([ 0., 0.])).all() + +def test_start_tw_array(): + blackboard = Mock(problem = problem) + knowledge_source = CreateServicesAttributesFromProblem(blackboard) + knowledge_source.process() + + assert (blackboard.start_tw == numpy.array([ 0., 0.])).all() + +def test_end_tw_array(): + blackboard = Mock(problem = problem) + knowledge_source = CreateServicesAttributesFromProblem(blackboard) + knowledge_source.process() + + assert (blackboard.end_tw == numpy.array([ -1., -1.])).all() diff --git a/unconstrained-initialization/knowledge_sources/tests/create_vehicles_attributes_from_problem_test.py b/unconstrained-initialization/knowledge_sources/tests/create_vehicles_attributes_from_problem_test.py new file mode 100644 index 000000000..8d17b573f --- /dev/null +++ b/unconstrained-initialization/knowledge_sources/tests/create_vehicles_attributes_from_problem_test.py @@ -0,0 +1,125 @@ +from unittest.mock import Mock, patch +import pytest +import copy +import numpy + +from knowledge_sources.create_vehicles_attributes_from_problem import CreateVehiclesAttributesFromProblem + +problem = { "vehicles": + [ + { + "id" : "1", + "costFixed": 1000, + "capacities": [ + { + "units" : "kg", + "limit" : 100, + "overloadMultiplier" : 100 + } + ], + "costDistanceMultiplier":1, + "costTimeMultiplier":1, + "timeWindow": { + "start":0, + "end" : 900, + "maximumLateness": 100 + }, + "distance": 1000, + "matrixIndex":0, + "startIndex": 0, + "endIndex": 0 + }, + { + "id" : "2", + "costFixed": 1000, + "capacities": [ + { + "units" : "kg", + "limit" : 100, + "overloadMultiplier" : 100 + } + ], + "costDistanceMultiplier":1, + "costTimeMultiplier":1, + "timeWindow": { + "start":0, + "end" : 900, + "maximumLateness": 10 + }, + "distance" : 1000, + "matrixIndex":0, + "startIndex": 2, + "endIndex": 0 + } + ], + "relations" : [{ + "type": "vehicle_trips", + "linkedVehicleIds": ["1", "2"] + } + ], + "services":[1,2] + } + +def test_arrays(): + blackboard = Mock(problem = copy.deepcopy(problem)) + + knowledge_source = CreateVehiclesAttributesFromProblem(blackboard) + + knowledge_source.process() + assert (blackboard.vehicles_distance_max == numpy.array([ 1000., 1000.])).all() + assert (blackboard.cost_time_multiplier == numpy.array([ 1., 1.])).all() + assert (blackboard.cost_distance_multiplier == numpy.array([ 1., 1.])).all() + assert (blackboard.vehicle_capacities == numpy.array([ 100., 100.])).all() + assert (blackboard.vehicles_TW_starts == numpy.array([ 0., 0.])).all() + assert (blackboard.vehicles_TW_ends == numpy.array([ 1000., 910.])).all() + assert (blackboard.previous_vehicle == numpy.array([ -1., 0.])).all() + assert (blackboard.vehicles_overload_multiplier == numpy.array([ 100., 100.])).all() + assert (blackboard.vehicle_end_index == numpy.array([ 2., 2.])).all() + assert (blackboard.vehicle_start_index == numpy.array([ 2., 3.])).all() + +def test_no_attributes_second_vehicles(): + blackboard = Mock(problem = copy.deepcopy(problem)) + del blackboard.problem["vehicles"][1]['distance'] + del blackboard.problem["vehicles"][1]['costTimeMultiplier'] + del blackboard.problem["vehicles"][1]['costDistanceMultiplier'] + knowledge_source = CreateVehiclesAttributesFromProblem(blackboard) + + knowledge_source.process() + + assert (blackboard.vehicles_distance_max == numpy.array([ 1000., -1.])).all() + assert (blackboard.cost_time_multiplier == numpy.array([ 1., 0.])).all() + assert (blackboard.cost_distance_multiplier == numpy.array([ 1., 0.])).all() + assert (blackboard.vehicle_capacities == numpy.array([ [100.], [100.]])).all() + assert (blackboard.vehicles_overload_multiplier == numpy.array([ [100.], [100.]])).all() + + + +def test_no_id(): + blackboard = Mock(problem = copy.deepcopy(problem)) + del blackboard.problem["vehicles"][1]['id'] + + knowledge_source = CreateVehiclesAttributesFromProblem(blackboard) + + with pytest.raises(AttributeError): + knowledge_source.verify() + + +def test_no_vehicle(): + blackboard = Mock(problem = copy.deepcopy(problem)) + del blackboard.problem["vehicles"] + + knowledge_source = CreateVehiclesAttributesFromProblem(blackboard) + + with pytest.raises(AttributeError): + knowledge_source.verify() + + +def test_no_multitour(): + blackboard = Mock(problem = copy.deepcopy(problem)) + del blackboard.problem["relations"] + + knowledge_source = CreateVehiclesAttributesFromProblem(blackboard) + + knowledge_source.process() + print(blackboard.previous_vehicle) + assert (blackboard.previous_vehicle == numpy.array([-1.,-1.])).all() diff --git a/unconstrained-initialization/knowledge_sources/tests/get_arguments_test.py b/unconstrained-initialization/knowledge_sources/tests/get_arguments_test.py new file mode 100644 index 000000000..1a1785fcb --- /dev/null +++ b/unconstrained-initialization/knowledge_sources/tests/get_arguments_test.py @@ -0,0 +1,87 @@ +from unittest.mock import Mock, patch +import pytest + +from knowledge_sources.get_arguments import GetArguments + +@patch("os.path.exists") +@patch('sys.argv', ["scrip.py", "-instance_file", "instance.txt", "-solution_file", "solution.txt"]) +def test_verify_missing_time_limit(file_exists): + file_exists.return_value = True + blackboard = Mock() + knowledge_source = GetArguments(blackboard) + + with pytest.raises(AttributeError): + knowledge_source.verify() + + + +@patch("os.path.exists") +@patch('sys.argv', ["scrip.py","-time_limit_in_ms", "8000", "-solution_file", "solution.txt"]) +def test_verify_missing_instance(file_exists): + file_exists.return_value = True + blackboard = Mock() + knowledge_source = GetArguments(blackboard) + + with pytest.raises(AttributeError): + knowledge_source.verify() + + + + +@patch("os.path.exists") +@patch('sys.argv', ["scrip.py","-time_limit_in_ms", "8000", "-instance_file", "instance.txt"]) +def test_verify_missing_solution_file(file_exists): + file_exists.return_value = True + blackboard = Mock() + knowledge_source = GetArguments(blackboard) + + with pytest.raises(AttributeError): + knowledge_source.verify() + + +@patch("os.path.exists") +@patch('sys.argv', ["scrip.py", "-instance_file", "instance.txt", "-solution_file", "solution.txt", "-time_limit_in_ms", "8000"]) +def test_verify_file_not_exist(file_exists): + file_exists.return_value = False + blackboard = Mock() + knowledge_source = GetArguments(blackboard) + + with pytest.raises(FileNotFoundError): + knowledge_source.verify() + + + +@patch('sys.argv', ["scrip.py", "-instance_file", "instance.txt", "-solution_file", "solution.txt", "-time_limit_in_ms", "coucou"]) +@patch("os.path.exists") +def test_verify_time_limit_not_numeric(file_exists): + file_exists.return_value = True + blackboard = Mock() + knowledge_source = GetArguments(blackboard) + + with pytest.raises(ValueError): + knowledge_source.verify() + + + +@patch("os.path.exists") +@patch('sys.argv', [ "scrip.py","-instance_file", "instance.txt", "-solution_file", "solution.txt", "-time_limit_in_ms", "8000"]) +def test_verify_ok(file_exists): + file_exists.return_value = True + blackboard = Mock() + knowledge_source = GetArguments(blackboard) + + assert knowledge_source.verify() == True + + + + +@patch("os.path.exists") +@patch('sys.argv', [ "scrip.py","-instance_file", "instance.txt", "-solution_file", "solution.txt", "-time_limit_in_ms", "8000"]) +def test_process(file_exists): + blackboard = Mock() + knowledge_source = GetArguments(blackboard) + knowledge_source.process() + + assert blackboard.time_limit == 8 + assert blackboard.instance == "instance.txt" + assert blackboard.output_file == "solution.txt" diff --git a/unconstrained-initialization/knowledge_sources/tests/process_clustering_initial_paths_test.py b/unconstrained-initialization/knowledge_sources/tests/process_clustering_initial_paths_test.py new file mode 100644 index 000000000..af0f2ff6d --- /dev/null +++ b/unconstrained-initialization/knowledge_sources/tests/process_clustering_initial_paths_test.py @@ -0,0 +1,44 @@ +from unittest.mock import Mock, patch, MagicMock +import pytest +import numpy +import copy + +from knowledge_sources.process_clustering_initial_paths import ProcessClusteringInitialPaths + +def paths_contains(paths, values): + + return (all(x in paths[0] for x in values[0]) and all(x in paths[1] for x in values[1])) or (all(x in paths[1] for x in values[0]) and all(x in paths[0] for x in values[1])) + +def test_time_matrix_is_not_square(): + time_matrices = numpy.array([[[1,2],[1,2],[1,2]]]) + blackboard = MagicMock(time_matrices = time_matrices) + knowledge_source = ProcessClusteringInitialPaths(blackboard) + with pytest.raises(AttributeError): + knowledge_source.verify() + +def test_matrix_is_none(): + blackboard = Mock(time_matrices = None) + knowledge_source = ProcessClusteringInitialPaths(blackboard) + with pytest.raises(AttributeError): + knowledge_source.verify() + +def test_process(): + blackboard = Mock(time_matrices = numpy.array([[[0,0,5,5,1,1], + [0,0,5,5,5,1], + [5,5,0,0,5,1], + [5,5,0,0,1,1], + [1,5,1,1,0,1], + [1,5,1,1,1,0]]]), + distance_matrices = numpy.array([[[0,1,1,1,1,1], + [1,0,1,5,5,1], + [1,1,0,5,5,1], + [1,5,1,0,1,1], + [1,5,1,1,0,1], + [1,5,1,1,1,0]]]), + vehicle_start_index = [4,5], + vehicle_end_index = [4,5], + num_vehicle = 2) + knowledge_source = ProcessClusteringInitialPaths(blackboard) + + knowledge_source.process() + assert paths_contains(blackboard.paths, [[0,1],[2,3]]) diff --git a/unconstrained-initialization/main.py b/unconstrained-initialization/main.py new file mode 100644 index 000000000..94a2a2703 --- /dev/null +++ b/unconstrained-initialization/main.py @@ -0,0 +1,85 @@ + +from blackboard.blackboard import Blackboard +from controller.controller import Controller +import traceback +import os, sys +import logging as log + +from knowledge_sources.create_services_attributes_from_problem import CreateServicesAttributesFromProblem +from knowledge_sources.create_vehicles_attributes_from_problem import CreateVehiclesAttributesFromProblem +from knowledge_sources.create_matrices_from_problem import CreateMatricesFromProblem +from knowledge_sources.deserialize_problem import DeserializeProblem +from knowledge_sources.get_arguments import GetArguments +from knowledge_sources.optimize_solution import OptimizeSolution +from knowledge_sources.process_clustering_initial_paths import ProcessClusteringInitialPaths +from knowledge_sources.process_initial_solution import ProcessInitialSolution +from knowledge_sources.create_services_attributes_from_problem import CreateServicesAttributesFromProblem +from knowledge_sources.parse_and_serialize_solution import ParseAndSerializeSolution +from knowledge_sources.print_kpis import PrintKpis +from knowledge_sources.create_dictionnary_index_to_id import CreateDictionnaryIndexId + + + + +def main(): + """Main function to run the model + """ + log_config() + log.info("----------Start working------------") + + try: + # Initialize the blackboard + blackboard = Blackboard() + + # Add the knowledge sources + blackboard.add_knowledge_source(GetArguments(blackboard)) + blackboard.add_knowledge_source(DeserializeProblem(blackboard)) + blackboard.add_knowledge_source(CreateVehiclesAttributesFromProblem(blackboard)) + blackboard.add_knowledge_source(CreateServicesAttributesFromProblem(blackboard)) + blackboard.add_knowledge_source(CreateDictionnaryIndexId(blackboard)) + blackboard.add_knowledge_source(CreateMatricesFromProblem(blackboard)) + blackboard.add_knowledge_source(ProcessClusteringInitialPaths(blackboard)) + blackboard.add_knowledge_source(ProcessInitialSolution(blackboard)) + blackboard.add_knowledge_source(OptimizeSolution(blackboard)) + blackboard.add_knowledge_source(ParseAndSerializeSolution(blackboard)) + blackboard.add_knowledge_source(PrintKpis(blackboard)) + + # Initialize the controller and run it + controller = Controller(blackboard) + controller.run_knowledge_sources() + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + error = f"{exc_type.__name__} : {e}\n" + "".join(traceback.format_tb(exc_tb)) + log.critical(error) + + log.info("-----------End working------------") + + + + +def log_config(): + """Setup the logger + + The logger will write its output in a new file every day at midnight + + Returns + ------- + log : logging.getLogger + Configurated logger to use in all the package + + """ + + # Constants + PATH_TO_LOG = os.path.join( + os.getcwd(), "init_vrp.log" + ) + LOG_FORMAT = "%(asctime)s | %(levelname)s\t | %(name)s\t : %(message)s" + LOGGING_MODE = log.INFO + + #(PATH_TO_LOG, when="midnight", interval=1, encoding="utf8") + log.basicConfig(filename=PATH_TO_LOG, format=LOG_FORMAT, level=LOGGING_MODE) + + +if __name__ == '__main__': + main() diff --git a/unconstrained-initialization/solution_parser.py b/unconstrained-initialization/solution_parser.py deleted file mode 100644 index 4ba0a24bd..000000000 --- a/unconstrained-initialization/solution_parser.py +++ /dev/null @@ -1,71 +0,0 @@ -import numpy -import timeit -import random -import logging -import json -from fastvrpy.core import algorithm -import cProfile -from sklearn.metrics import pairwise_distances -from sklearn.cluster import KMeans, AgglomerativeClustering, OPTICS, SpectralClustering, MiniBatchKMeans, DBSCAN - -import localsearch_result_pb2 -import math -import numpy -from google.protobuf.json_format import MessageToDict -import sys - - -def ParseSolution(solution, vrp): - paths = numpy.asarray(solution.paths) - result = localsearch_result_pb2.Result() - routes = [] - relations = [] - for index,path in enumerate(paths): - services_ids = [ "service" + str(stops-1) for stops in path if stops != -1 ] - vehicle_id = f"vehic{index}_0" - dic = {"vehicle_id": vehicle_id, "mission_ids" : services_ids} - dic1 = {"type" : "order", "linked_ids" : services_ids} - routes.append(dic) - relations.append(dic1) - - - with open("result.json", "w") as file: - json.dump(routes, file, indent=4) - with open("relations.json", "w") as file: - json.dump(relations, file, indent=4) - - - for path_index,path in enumerate(paths): - if len(set(path)) > 1 : - route = result.routes.add() - store = route.activities.add() - store.id = "store" - store.index = -1 - store.start_time = int(solution.vehicle_starts[path_index]) - store.type = "start" - for stop_index,stop in enumerate(path): - if stop != -1 : - activity = route.activities.add() - activity.id = "service" + str(stop-1) - activity.index = stop-1 - activity.start_time = int(solution.starts[path_index, stop_index+1]) - activity.type = "service" - store_return = route.activities.add() - store_return.id = "store" - store_return.index = -1 - store_return.start_time = int(solution.vehicle_ends[path_index]) - store_return.type = "end" - cost_details = route.cost_details - cost_details.fixed = 0 - else: - route = result.routes.add() - start_route = route.activities.add() - start_route.type ="start" - start_route.index = -1 - start_route.start_time = 0 - end_route = route.activities.add() - end_route.type ="start" - end_route.index = -1 - end_route.start_time = 0 - - return result diff --git a/unconstrained-initialization/tests/__init__.py b/unconstrained-initialization/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/unconstrained-initialization/wrapper.py b/unconstrained-initialization/wrapper.py deleted file mode 100644 index d078fa0dd..000000000 --- a/unconstrained-initialization/wrapper.py +++ /dev/null @@ -1,259 +0,0 @@ -import numpy -import timeit -import random -import logging as log -from fastvrpy.core import algorithm -import cProfile -from sklearn.metrics import pairwise_distances -from sklearn.cluster import KMeans, AgglomerativeClustering, OPTICS, SpectralClustering, MiniBatchKMeans, DBSCAN -from fastvrpy.utils import * -import localsearch_vrp_pb2 -import math -import numpy -from google.protobuf.json_format import MessageToDict -import sys -import json -from fastvrpy import solver -from solution_parser import ParseSolution - -log_config() -root_log = log.getLogger("root") -root_log.setLevel(log.INFO) -wrapper_log = log.getLogger("wrapper") -wrapper_log.setLevel(log.INFO) - -args = sys.argv[1:] - -index = args.index("-time_limit_in_ms") -duration = int(args[index + 1]) / 1000 - -for arg in sys.argv : - if "optimize-or-tools-input" in arg: - instance = arg - if "optimize-or-tools-output" in arg: - solution_file = arg - -log.info("Start Initial Solution") - -with open(instance, 'rb') as f: - problem = localsearch_vrp_pb2.Problem() - problem.ParseFromString(f.read()) - problem = MessageToDict(problem, including_default_value_fields=True) - - # Vehicle attributes - num_vehicles = len(problem['vehicles']) - cost_distance_multiplier = [] - cost_time_multiplier = [] - vehicles_capacity = [] - vehicles_TW_starts = [] - vehicles_TW_ends = [] - vehicles_distance_max = [] - vehicles_fixed_costs = [] - vehicles_overload_multiplier = [] - for vehicle in problem['vehicles'] : - if vehicle["costFixed"]: - vehicles_fixed_costs.append(vehicle["costFixed"]) - else: - vehicles_fixed_costs.append(0) - if len(vehicle['capacities']) > 0: - vehicles_capacity.append(vehicle['capacities'][0]['limit']) - if vehicle['capacities'][0]["overloadMultiplier"]: - vehicles_overload_multiplier.append(vehicle['capacities'][0]["overloadMultiplier"]) - else: - vehicles_overload_multiplier.append(0) - else : - vehicles_capacity.append(-1) - if vehicle['costDistanceMultiplier']: - cost_distance_multiplier.append(vehicle['costDistanceMultiplier']) - else : - cost_distance_multiplier.append(0) - if vehicle['costTimeMultiplier']: - cost_time_multiplier.append(vehicle['costTimeMultiplier']) - else : - cost_time_multiplier.append(0) - if vehicle['timeWindow']: - if 'start' in vehicle['timeWindow']: - vehicles_TW_starts.append(vehicle['timeWindow']["start"]) - else : - vehicles_TW_starts.append(0) - if 'maximumLateness' in vehicle['timeWindow']: - vehicles_TW_ends.append(vehicle['timeWindow']["end"] + vehicle['timeWindow']["maximumLateness"]) - else : - vehicles_TW_ends.append(vehicle['timeWindow']["end"]) - else : - vehicles_TW_starts.append(0) - vehicles_TW_ends.append(-1) - if vehicle['distance']: - vehicles_distance_max.append(vehicle['distance']) - else : - vehicles_distance_max.append(-1) - - # # Build Matrices : - time_matrices = [] - distance_matrices = [] - vehicle_index = 0 - for matrice in problem['matrices']: - vehicle = problem['vehicles'][vehicle_index] - time_matrix = [] - time_matrix_size = int(math.sqrt(len(matrice['time']))) - timeToWarehouse =[] - timeToWarehouse.append(matrice['time'][vehicle['endIndex'] * time_matrix_size + vehicle['endIndex']]) - for service in problem['services']: - timeToWarehouse.append(matrice['time'][vehicle['startIndex'] * time_matrix_size + service['matrixIndex'] ]) - time_matrix.append(timeToWarehouse) - for serviceFrom in problem['services']: - # print(serviceFrom) - matrix_row = [] - matrix_row.append(matrice['time'][ serviceFrom['matrixIndex']* time_matrix_size + vehicle['endIndex'] ]) - for serviceTo in problem['services']: - matrix_row.append(matrice['time'][serviceFrom['matrixIndex'] * time_matrix_size + serviceTo['matrixIndex']]) - time_matrix.append(matrix_row) - time_matrices.append(time_matrix) - vehicle_index += 1 - - vehicle_index = 0 - for matrice in problem['matrices']: - vehicle = problem['vehicles'][vehicle_index] - distance_matrix = [] - distance_matrix_size = int(math.sqrt(len(matrice['distance']))) - distanceToWarehouse =[] - distanceToWarehouse.append(matrice['distance'][vehicle['endIndex'] * distance_matrix_size + vehicle['endIndex']]) - for service in problem['services']: - distanceToWarehouse.append(matrice['distance'][vehicle['startIndex'] * distance_matrix_size + service['matrixIndex'] ]) - distance_matrix.append(distanceToWarehouse) - for serviceFrom in problem['services']: - # print(serviceFrom) - matrix_row = [] - matrix_row.append(matrice['distance'][ serviceFrom['matrixIndex']* distance_matrix_size + vehicle['endIndex'] ]) - for serviceTo in problem['services']: - matrix_row.append(matrice['distance'][serviceFrom['matrixIndex'] * distance_matrix_size + serviceTo['matrixIndex']]) - distance_matrix.append(matrix_row) - distance_matrices.append(distance_matrix) - vehicle_index += 1 - - # Services attributes - services_TW_starts = [0] - services_TW_ends = [200000] - services_duration = [0] - services_setup_duration = [0] - services_quantities = [0] - for service in problem['services']: - if len(service['timeWindows']) > 0: - services_TW_starts.append(service['timeWindows'][0]['start']) - if "maximumLateness" in service['timeWindows'][0] and service['lateMultiplier'] > 0: - maxLateness = service['timeWindows'][0]["maximumLateness"] - else : - maxLateness = 0 - services_TW_ends.append(service['timeWindows'][0]['end'] + maxLateness) - else : - services_TW_starts.append(0) - services_TW_ends.append(-1) - services_duration.append(service['duration']) - if "setupDuration" in service: - services_setup_duration.append(service['setupDuration']) - else : - services_setup_duration.append(0) - if len(service['quantities']) > 0 : - services_quantities.append(service['quantities'][0]) - else : - services_quantities.append(0) - - - # Services attributes - start_tw = numpy.array(services_TW_starts, dtype=numpy.float64) - end_tw = numpy.array(services_TW_ends, dtype=numpy.float64) - durations = numpy.array(services_duration, dtype=numpy.float64) - setup_durations = numpy.array(services_setup_duration, dtype=numpy.float64) - services_quantities = numpy.array(services_quantities, dtype=numpy.int32) - - # Vehicles attributes - cost_time_multiplier = numpy.array(cost_time_multiplier, dtype=numpy.float64) - cost_distance_multiplier = numpy.array(cost_distance_multiplier, dtype=numpy.float64) - vehicle_capacity = numpy.array(vehicles_capacity, dtype=numpy.float64) - vehicles_TW_starts = numpy.array(vehicles_TW_starts, dtype=numpy.float64) - vehicles_TW_ends = numpy.array(vehicles_TW_ends, dtype=numpy.float64) - vehicles_distance_max = numpy.array(vehicles_distance_max, dtype=numpy.float64) - vehicles_fixed_costs = numpy.array(vehicles_fixed_costs, dtype=numpy.float64) - vehicles_overload_multiplier = numpy.array(vehicles_overload_multiplier, dtype=numpy.float64) - - # Matrices - distance_matrix = numpy.array(distance_matrices, dtype=numpy.float64) - time_matrix = numpy.array(time_matrices, dtype=numpy.float64) - - # numpy.savetxt("distance_matrix.txt", json.dumps(list(distance_matrix))) - # numpy.savetxt("time_matrix.txt", json.dumps(list(time_matrix))) - - SIZE = len(problem['services']) - NUM_VEHICLE = len(problem['vehicles']) - if 'capacities' in problem['vehicles'][0] and len(problem['vehicles'][0]['capacities']) > 0: - MAX_CAPACITY = int(problem['vehicles'][0]['capacities'][0]['limit']) - else: - MAX_CAPACITY = 2**30 - - - - # #Cost factors - previous_vehicle = numpy.array([ -1 for _ in range(NUM_VEHICLE)], dtype= numpy.int32) - vehicle_id_index = {} - vehicle_index = 0 - for vehicle in problem['vehicles']: - vehicle_id_index[vehicle['id']] = vehicle_index - vehicle_index += 1 - - for relation in problem['relations']: - if relation['type'] == "vehicle_trips": - size = len(relation['linkedVehicleIds']) - for i in range(size): - if i == 0 : - previous_vehicle[vehicle_id_index[relation['linkedVehicleIds'][i]]] = -1 - else : - previous_vehicle[vehicle_id_index[relation['linkedVehicleIds'][i]]] = vehicle_id_index[relation['linkedVehicleIds'][i-1]] - - - log.info("Init parameters") - # path_init = list(range(1,SIZE+1)) - - random.seed(22021993) - numpy.random.seed(22021993) - - - cost_matrix = 0.3 * distance_matrix + 15 * time_matrix - - services_volume = numpy.array(services_quantities, dtype=numpy.float64) - - paths = process_initial_solution(NUM_VEHICLE, time_matrix[0]) - - - - solution = algorithm.Solution( - paths, - distance_matrix, - time_matrix, - start_tw, - end_tw, - durations, - setup_durations, - services_volume, - cost_distance_multiplier, - cost_time_multiplier, - vehicle_capacity, - previous_vehicle, - vehicles_distance_max, - vehicles_fixed_costs, - vehicles_overload_multiplier, - vehicles_TW_starts, - vehicles_TW_ends - ) - - solver.optimize( - solution = solution, - max_execution_time=int(duration/2), - problem=problem, - groups_max_capacity = MAX_CAPACITY - ) - parsed_solution = ParseSolution(solution, problem) - f = open(solution_file, "wb") - f.write(parsed_solution.SerializeToString()) - f.close() - - print_kpis(solution) diff --git a/wrappers/unconstrained_initialization.rb b/wrappers/unconstrained_initialization.rb index 6fa762c7c..42ac195d2 100644 --- a/wrappers/unconstrained_initialization.rb +++ b/wrappers/unconstrained_initialization.rb @@ -21,7 +21,7 @@ module Wrappers class UnconstrainedInitialization < Ortools def initialize(hash = {}) - hash[:exec_ortools] = 'python3 unconstrained-initialization/wrapper.py' + hash[:exec_ortools] = 'python3 unconstrained-initialization/main.py' super(hash) end end