From 556aad2f5eebbd78785de210508ae412f5b87b22 Mon Sep 17 00:00:00 2001 From: Hoyeol Yoon <9911hoho@gmail.com> Date: Tue, 6 Aug 2024 21:47:58 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=EC=A4=91=EC=A0=90=EC=9D=84=20=EC=9D=B4?= =?UTF-8?q?=EC=9A=A9=ED=95=9C=20bundle=20merge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Mid_point_240805/Mid_point_240805.zip | Bin 0 -> 9456 bytes Mid_point_240805/custom_util.py | 145 ++++++++ Mid_point_240805/myalgorithm.py | 233 +++++++++++++ Mid_point_240805/util.py | 470 ++++++++++++++++++++++++++ 4 files changed, 848 insertions(+) create mode 100644 Mid_point_240805/Mid_point_240805.zip create mode 100644 Mid_point_240805/custom_util.py create mode 100644 Mid_point_240805/myalgorithm.py create mode 100644 Mid_point_240805/util.py diff --git a/Mid_point_240805/Mid_point_240805.zip b/Mid_point_240805/Mid_point_240805.zip new file mode 100644 index 0000000000000000000000000000000000000000..953b23742ac1593ff0e777aca4233e46aad22489 GIT binary patch literal 9456 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 0 : + if total_volume <= rider.capa and len(merged_orders) <= 5: + for shop_pem in permutations(merged_orders): + for dlv_pem in permutations(merged_orders): + feasibility_check = test_route_feasibility(all_orders, rider, shop_pem, dlv_pem) + if feasibility_check == 0: # feasible! + total_dist = get_total_distance(K, dist_mat, shop_pem, dlv_pem) + temp_bundle = Bundle(all_orders, rider, list(shop_pem), list(dlv_pem), bundle1.total_volume + bundle2.total_volume, total_dist) + temp_bundle.update_cost() + + if temp_bundle.cost < min_total_cost: + min_total_cost = temp_bundle.cost + best_bundle = temp_bundle + + return best_bundle + +def custom_try_bundle_rider_changing(all_orders, dist_mat, bundle, all_riders): + old_rider = bundle.rider + best_shop_seq = None + best_dlv_seq = None + best_rider = None + min_total_cost = float('inf') + + for rider in all_riders: + if bundle.total_volume <= rider.capa: + orders = bundle.shop_seq + + for shop_pem in permutations(orders): + for dlv_pem in permutations(orders): + feasibility_check = test_route_feasibility(all_orders, rider, shop_pem, dlv_pem) + if feasibility_check == 0: # feasible! + total_dist = get_total_distance(len(all_orders), dist_mat, shop_pem, dlv_pem) + bundle.shop_seq = list(shop_pem) + bundle.dlv_seq = list(dlv_pem) + bundle.rider = rider + bundle.total_dist = total_dist + bundle.update_cost() + if bundle.cost < min_total_cost: + min_total_cost = bundle.cost + best_shop_seq = list(shop_pem) + best_dlv_seq = list(dlv_pem) + best_rider = rider + + if best_shop_seq and best_dlv_seq and best_rider: + # Note: in-place replacing! + bundle.shop_seq = best_shop_seq + bundle.dlv_seq = best_dlv_seq + bundle.rider = best_rider + bundle.total_dist = get_total_distance(len(all_orders), dist_mat, best_shop_seq, best_dlv_seq) + bundle.update_cost() # update the cost with the best sequences and rider + if old_rider != best_rider : + old_rider.available_number += 1 + best_rider.available_number -= 1 + return True + + return False + +def count_bundles(all_bundles): + counts = { + 'WALK': {'total': 0, 'lengths': {}}, + 'BIKE': {'total': 0, 'lengths': {}}, + 'CAR': {'total': 0, 'lengths': {}} + } + + # 각 요소를 순회하며 카운팅 + for bundle in all_bundles: + transport_type = bundle.rider.type + counts[transport_type]['total'] += 1 + + length = len(bundle.shop_seq) # `shop_seq`의 길이를 기준으로 한다. + if length not in counts[transport_type]['lengths']: + counts[transport_type]['lengths'][length] = 0 + counts[transport_type]['lengths'][length] += 1 + + # 결과 출력 + for transport_type, data in counts.items(): + total_count = data['total'] + print(f"{transport_type}: 총 {total_count}개") + for length, count in sorted(data['lengths'].items()): + print(f" 길이 {length}: {count}개") + +def avg_loc(all_orders, all_bundles): + bundles_index = [bundle.shop_seq for bundle in all_bundles] + bundles_avg_loc = [] + for index_seq in bundles_index: + ords_loc = [((order.shop_lat, order.shop_lon), (order.dlv_lat, order.dlv_lon)) for order in all_orders if order.id in index_seq] + bundle_loc = np.zeros((2,2)) + for shop_loc, dlv_loc in ords_loc: + bundle_loc[0] += np.array(shop_loc) + bundle_loc[1] += np.array(dlv_loc) + bundle_loc /= len(ords_loc) + bundles_avg_loc.append(bundle_loc) + + return bundles_avg_loc + +def haversine_distance(coord1, coord2): + lat1, lon1 = coord1 + lat2, lon2 = coord2 + R = 6371.0 # 지구의 반지름 (킬로미터) + + phi1 = math.radians(lat1) + phi2 = math.radians(lat2) + delta_phi = math.radians(lat2 - lat1) + delta_lambda = math.radians(lon2 - lon1) + + a = math.sin(delta_phi / 2.0)**2 + \ + math.cos(phi1) * math.cos(phi2) * \ + math.sin(delta_lambda / 2.0)**2 + c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)) + + distance = R * c + return distance + +def dist_mat_by_loc(all_bundles_avg_loc): + + N = len(all_bundles_avg_loc) + bundles_dist_mat = np.zeros((2 * N, 2 * N)) + + for i in range(N): + for j in range(N): + # 픽업 지점 간 거리 + bundles_dist_mat[i][j] = haversine_distance(all_bundles_avg_loc[i][0], all_bundles_avg_loc[j][0]) + + # 배송 지점과 픽업 지점 간 거리 + bundles_dist_mat[i + N][j] = haversine_distance(all_bundles_avg_loc[i][1], all_bundles_avg_loc[j][0]) + + # 픽업 지점과 배송 지점 간 거리 + bundles_dist_mat[i][j + N] = haversine_distance(all_bundles_avg_loc[i][0], all_bundles_avg_loc[j][1]) + + # 배송 지점 간 거리 + bundles_dist_mat[i + N][j + N] = haversine_distance(all_bundles_avg_loc[i][1], all_bundles_avg_loc[j][1]) + + return bundles_dist_mat \ No newline at end of file diff --git a/Mid_point_240805/myalgorithm.py b/Mid_point_240805/myalgorithm.py new file mode 100644 index 0000000..0ebedf2 --- /dev/null +++ b/Mid_point_240805/myalgorithm.py @@ -0,0 +1,233 @@ +from util import * +from custom_util import * +import heapq + +def algorithm(K, all_orders, all_riders, dist_mat, timelimit=60): + + start_time = time.time() + + print('Code Start') + print('---------------------------------------------------------------------------------------') + + for r in all_riders: + r.T = np.round(dist_mat/r.speed + r.service_time) + + # A solution is a list of bundles + solution = [] + + #------------- Custom algorithm code starts from here --------------# + + walk_rider = None + for r in all_riders: + if r.type == 'WALK': + walk_rider = r + + car_rider = None + for r in all_riders: + if r.type == 'CAR': + car_rider = r + + all_bundles = [] + all_orders_tmp = all_orders.copy() + + heap = [] + cant_walk_list = [] + filt_ord = [] + + for rider in all_riders: + if rider.type == "WALK": + walk_speed = rider.speed #도보 속도 + walk_time_mat = np.round(dist_mat/rider.speed + rider.service_time) #도보 이동시간 + break + + for order in all_orders_tmp: + ready_time = order.order_time + order.cook_time + time_diff = order.deadline - ready_time #해당 order의 준비~데드라인의 시간 차이 + walk_time = walk_time_mat[order.id][order.id+K] #해당 order를 배송하기 위해 도보로 이동할 떄 필요한 시간 + + if time_diff < walk_time: #만약 주어진 시간이 모자라면 + cant_walk_list.append(order.id) #배달 불가능한 배달 번호 추가 + + fcut_orders = [order for order in all_orders_tmp if order.id not in cant_walk_list] #불가능한 orders를 첫번째 잘라내고 남은 orders + + for f_order in fcut_orders: + cant_merge_list = [f_order.id] + for s_order in fcut_orders: + #첫번째 order의 데드라인보다 두번째 order의 레디가 더 늦은 경우 cut + if f_order.deadline < s_order.order_time + s_order.cook_time or f_order.order_time + f_order.cook_time > s_order.deadline: + cant_merge_list.append(s_order.id) + # 두번째 order의 레디에 2픽업->1도착의 이동 시간을 더해서 첫번째 order의 데드라인보다 늦으면 cut + elif s_order.order_time + s_order.cook_time + walk_time_mat[s_order.id][f_order.id+K] > f_order.deadline: + cant_merge_list.append(s_order.id) + #print(f"{f_order.id}번째 order는 {cant_merge_list}와 결합 불가능") + scut_orders = [order for order in fcut_orders if order.id not in cant_merge_list] #불가능한 orders를 두번째 잘라내고 남은 orders + + #Cut 이후에 merging 작업 진행 + ord = f_order + new_bundle = Bundle(all_orders, walk_rider, [ord.id], [ord.id], ord.volume, dist_mat[ord.id, ord.id + K]) + + for s_ord in scut_orders: + new_bundle.shop_seq.append(s_ord.id) + new_bundle.dlv_seq.append(s_ord.id) + + for dlv_pem in permutations(new_bundle.dlv_seq): + feasibility_check = test_route_feasibility(all_orders, walk_rider, new_bundle.shop_seq, dlv_pem) + if feasibility_check == 0: # feasible! + cost_1 = walk_rider.calculate_cost(dist_mat[ord.id, ord.id + K]) + cost_2 = walk_rider.calculate_cost(dist_mat[s_ord.id, s_ord.id + K]) + fea_bundle = Bundle(all_orders, walk_rider, new_bundle.shop_seq[:], list(dlv_pem), + new_bundle.total_volume + s_ord.volume, get_total_distance(K, dist_mat, new_bundle.shop_seq, dlv_pem)) + fea_bundle.update_cost() + cost_new = fea_bundle.cost + cost_diff = cost_1 + cost_2 - cost_new + heapq.heappush(heap, [-cost_diff, fea_bundle.shop_seq, fea_bundle.dlv_seq, fea_bundle.total_volume, fea_bundle.total_dist]) + + new_bundle.shop_seq.pop() + new_bundle.dlv_seq.pop() + + while heap: + smallest = heapq.heappop(heap) + if all(item not in filt_ord for item in smallest[1]): + filt_ord.extend(smallest[1]) + good_bundle = Bundle(all_orders, walk_rider, smallest[1], smallest[2], smallest[3], smallest[4]) + all_bundles.append(good_bundle) + walk_rider.available_number -= 1 + + print(f'time: {time.time()-start_time}') + count_bundles(all_bundles) + print('---------------------------------------------------------------------------------------') + + # Update all_orders_tmp + all_orders_tmp = [order for order in all_orders_tmp if order.id not in filt_ord] + + # Create initial bundles using a greedy approach based on distance + while all_orders_tmp: + ord = all_orders_tmp.pop(0) + #일단 car_rider를 넣어 feasible한 bundle을 찾음 + new_bundle = Bundle(all_orders, car_rider, [ord.id], [ord.id], ord.volume, dist_mat[ord.id, ord.id + K]) + # Try to add the nearest orders to the current bundle + while True: + nearest_order = None + min_dist = float('inf') + for other_ord in all_orders_tmp: + dist = dist_mat[ord.id, other_ord.id] + dist_mat[ord.id + K, other_ord.id + K] + (dist_mat[ord.id, ord.id + K] + dist_mat[ord.id, other_ord.id + K] + dist_mat[ord.id + K, other_ord.id] + dist_mat[other_ord.id, other_ord.id + K])*0.25 + if dist < min_dist and new_bundle.total_volume + other_ord.volume <= car_rider.capa: + min_dist = dist + nearest_order = other_ord + + if nearest_order: + new_bundle.shop_seq.append(nearest_order.id) + new_bundle.dlv_seq.append(nearest_order.id) + new_bundle.total_volume += nearest_order.volume + new_bundle.total_dist += min_dist + + feasibility_check = test_route_feasibility(all_orders, car_rider, new_bundle.shop_seq, new_bundle.dlv_seq) + if feasibility_check == 0: # Feasible + car_rider.available_number -= 1 + all_orders_tmp.remove(nearest_order) + new_bundle.update_cost() + custom_try_bundle_rider_changing(all_orders, dist_mat, new_bundle, all_riders) + else: + # Remove last added order if not feasible + new_bundle.shop_seq.pop() + new_bundle.dlv_seq.pop() + new_bundle.total_volume -= nearest_order.volume + new_bundle.total_dist -= min_dist + break + + else: + break + + all_bundles.append(new_bundle) + best_obj = sum((bundle.cost for bundle in all_bundles)) / K + print(f'time: {time.time()-start_time}') + print(f'Best obj = {best_obj}') + + count_bundles(all_bundles) + print('---------------------------------------------------------------------------------------') + + #print(all_bundles) + + while True: + iter = 0 + len_one = len([bundle for bundle in all_bundles if len(bundle.shop_seq) == 1]) + print(f'number of len one bundles: {len_one}') + + #각 번들들의 픽업과 배송 지점의 평균 위치 계산 + all_bundles_avg_loc = avg_loc(all_orders, all_bundles) + + #평균 위치를 토대로 각 번들들 간의 거리 행렬 생성 + all_bundles_dist_mat = dist_mat_by_loc(all_bundles_avg_loc) + + N = len(all_bundles) + + dist_heap = [] + + for i in range(N): + for j in range(i+1, N): + dist = all_bundles_dist_mat[i, j] + all_bundles_dist_mat[i+N, j+N] + (all_bundles_dist_mat[i, i+N] + all_bundles_dist_mat[i, j+N] + all_bundles_dist_mat[i+N, j] + all_bundles_dist_mat[j, j+N])*0.25 + + heapq.heappush(dist_heap, [dist, i, len(all_bundles[i].shop_seq), j, len(all_bundles[j].shop_seq)]) + + if len_one >= 3: + len_one_heap = [item for item in dist_heap if item[2] == 1 or item[4] == 1] + heapq.heapify(len_one_heap) + dist_heap = len_one_heap + + while dist_heap: + smallest = heapq.heappop(dist_heap) + + bundle1 = all_bundles[smallest[1]] + bundle2 = all_bundles[smallest[3]] + new_bundle = custom_try_merging_bundles(K, dist_mat, all_orders, bundle1, bundle2, all_riders) + + if new_bundle is not None: + all_bundles.remove(bundle1) + bundle1.rider.available_number += 1 + + all_bundles.remove(bundle2) + bundle2.rider.available_number += 1 + + all_bundles.append(new_bundle) + new_bundle.rider.available_number -= 1 + + cur_obj = sum((bundle.cost for bundle in all_bundles)) / K + if cur_obj < best_obj: + best_obj = cur_obj + print(f'time: {time.time()-start_time}') + print(f'Best obj = {best_obj}') + count_bundles(all_bundles) + print('---------------------------------------------------------------------------------------') + break + + else: + iter += 1 + + if time.time() - start_time > timelimit: + break + + if time.time() - start_time > timelimit: + break + + cur_obj = sum((bundle.cost for bundle in all_bundles)) / K + if cur_obj < best_obj: + best_obj = cur_obj + print(f'time: {time.time()-start_time}') + print(f'Best obj = {best_obj}') + count_bundles(all_bundles) + print('---------------------------------------------------------------------------------------') + print(iter) + + # Solution is a list of bundle information + solution = [ + # rider type, shop_seq, dlv_seq + [bundle.rider.type, bundle.shop_seq, bundle.dlv_seq] + for bundle in all_bundles + ] + + #------------- End of custom algorithm code--------------# + + + + return solution + \ No newline at end of file diff --git a/Mid_point_240805/util.py b/Mid_point_240805/util.py new file mode 100644 index 0000000..7707e48 --- /dev/null +++ b/Mid_point_240805/util.py @@ -0,0 +1,470 @@ + +import json +import numpy as np +from itertools import permutations +import random +import time +import pprint + +import matplotlib.pyplot as plt + + +# Change history +# 2024/7/20 - Fixed a bug in solution_check() to make sure pickups == deliveries +# 2024/7/1 - Update total_dist for a new bundle as well in try_bundle_rider_changing() +# 2024/6/21 - Fixed a comment in Order.__init__() +# 2024/6/16 - Fixed a bug that does not set the bundle routes in try_bundle_rider_changing() +# 2024/5/17 - Fixed a bug in get_pd_times() + + +# 주문 class +class Order: + def __init__(self, order_info): + # [ORD_ID, ORD_TIME, SHOP_LAT, SHOP_LON, DLV_LAT, DLV_LON, COOK_TIME, VOL, DLV_DEADLINE] + self.id = order_info[0] + self.order_time = order_info[1] + self.shop_lat = order_info[2] + self.shop_lon = order_info[3] + self.dlv_lat = order_info[4] + self.dlv_lon = order_info[5] + self.cook_time = order_info[6] + self.volume = order_info[7] + self.deadline = order_info[8] + + self.ready_time = self.order_time + self.cook_time + + def __repr__(self) -> str: + return f'Order([{self.id}, {self.order_time}, {self.shop_lat}, {self.shop_lon}, {self.dlv_lat}, {self.dlv_lon}, {self.volume}, {self.cook_time}, {self.deadline}])' + +# 배달원 class +class Rider: + def __init__(self, rider_info): + # [type, speed, capa, var_cost, fixed_cost, service_time, available number] + self.type = rider_info[0] + self.speed = rider_info[1] + self.capa = rider_info[2] + self.var_cost = rider_info[3] + self.fixed_cost = rider_info[4] + self.service_time = rider_info[5] + self.available_number = rider_info[6] + + def __repr__(self) -> str: + return f'Rider([{self.type}, {self.speed}, {self.capa}, {self.var_cost}, {self.fixed_cost}, {self.service_time}, {self.available_number}])' + + # 주어진 거리에 대한 배달원 비용 계산 + # = 배달원별 고정비 + 이동거리로 계산된 변동비 + def calculate_cost(self, dist): + return self.fixed_cost + dist / 100.0 * self.var_cost + +# 묶음 주문 정보 +class Bundle: + def __init__(self, all_orders, rider, shop_seq, dlv_seq, total_volume, total_dist, feasible=True): + self.rider = rider + self.all_orders = all_orders + self.feasible = feasible + self.shop_seq = shop_seq + self.dlv_seq = dlv_seq + self.total_volume = total_volume + self.total_dist = total_dist + self.update_cost() + + # 묶음 주문의 비용 update + def update_cost(self): + self.cost = self.rider.calculate_cost(self.total_dist) + self.cost_per_ord = self.cost / len(self.shop_seq) + + + def __repr__(self) -> str: + return f'Bundle(all_orders, {self.rider.type}, {self.shop_seq}, {self.dlv_seq}, {self.total_volume}, {self.feasible})' + + +# 주문들의 총 부피 계산 +# shop_seq는 주문들의 pickup list +# Note: shop_seq는 주문 id의 list와 동일 +def get_total_volume(all_orders, shop_seq): + return sum(all_orders[k].volume for k in shop_seq) + +# shop_seq의 순서로 pickup하고 dlv_seq 순서로 배달할 때 총 거리 계산 +# Note: shop_seq 와 dlv_seq는 같은 주문 id들을 가져야 함. 즉, set(shop_seq) == seq(dlv_seq). (주문 id들의 순서는 바뀔 수 있음) +def get_total_distance(K, dist_mat, shop_seq, dlv_seq): + return sum(dist_mat[i,j] for (i,j) in zip(shop_seq[:-1], shop_seq[1:])) + dist_mat[shop_seq[-1], dlv_seq[0]+K] + sum(dist_mat[i+K,j+K] for (i,j) in zip(dlv_seq[:-1], dlv_seq[1:])) + +# shop_seq의 순서로 pickup하고 dlv_seq 순서로 배달할 때 pickup과 delivery시간을 반환 +# Note: shop_seq 와 dlv_seq는 같은 주문 id들을 가져야 함. 즉, set(shop_seq) == seq(dlv_seq). (주문 id들의 순서는 바뀔 수 있음) +def get_pd_times(all_orders, rider, shop_seq, dlv_seq): + + K = len(all_orders) + + pickup_times = {} + + k = shop_seq[0] + t = all_orders[k].order_time + all_orders[k].cook_time # order time + order cook time + pickup_times[k] = t + for next_k in shop_seq[1:]: + t = max(t+rider.T[k, next_k], all_orders[next_k].ready_time) # max{travel time + service time, ready time} + pickup_times[next_k] = t + + k = next_k + + dlv_times = {} + + k = dlv_seq[0] + t += rider.T[shop_seq[-1], k + K] + + dlv_times[k] = t + + for next_k in dlv_seq[1:]: + t += rider.T[k + K, next_k + K] + + dlv_times[next_k] = t + + k = next_k + + return pickup_times, dlv_times + +# shop_seq의 순서로 pickup하고 dlv_seq 순서로 배달원 rider가 배달할 때 묶음주문 제약 만족 여부 테스트 +# 모든 제약을 만족하면 0 반환 +# 용량 제약을 만족하지 못하면 -1 반환 +# 시간 제약을 만족하지 못하면 -2 반환 +# Note: shop_seq 와 dlv_seq는 같은 주문 id들을 가져야 함. 즉, set(shop_seq) == seq(dlv_seq). (주문 id들의 순서는 바뀔 수 있음) +def test_route_feasibility(all_orders, rider, shop_seq, dlv_seq): + + total_vol = get_total_volume(all_orders, shop_seq) + if total_vol > rider.capa: + # Capacity overflow! + return -1 # Capacity infeasibility + + pickup_times, dlv_times = get_pd_times(all_orders, rider, shop_seq, dlv_seq) + + for k, dlv_time in dlv_times.items(): + if dlv_time > all_orders[k].deadline: + return -2 # Deadline infeasibility + + return 0 + +# 두 개의 bundle이 제약을 만족하면서 묶일 수 있는지 테스트 +# 합쳐진 붂음배송 경로는 가능한 모든 pickup/delivery 조합을 확인 +# 두 개의 bundle을 합치는게 가능하면 합쳐진 새로운 bundle을 반환 +# 합치는게 불가능하면 None을 반환 +# Note: 이 때 배달원은 두 개의 주어진 bundle을 배달하는 배달원들만 후보로 테스트(주어진 bundle에 사용되지 않는 배달원을 묶는게 가능할 수 있음!) +# Note: 여러개의 배달원으로 묶는게 가능할 때 가장 먼저 가능한 배달원 기준으로 반환(비용을 고려하지 않음) +def try_merging_bundles(K, dist_mat, all_orders, bundle1, bundle2): + merged_orders = bundle1.shop_seq + bundle2.shop_seq + total_volume = get_total_volume(all_orders, merged_orders) + if bundle1.rider.type == bundle2.rider.type: + riders = [bundle1.rider] + else: + riders = [bundle1.rider, bundle2.rider] + for rider in riders: + # We skip the test if there are too many orders + if total_volume <= rider.capa and len(merged_orders) <= 5: + for shop_pem in permutations(merged_orders): + for dlv_pem in permutations(merged_orders): + feasibility_check = test_route_feasibility(all_orders, rider, shop_pem, dlv_pem) + if feasibility_check == 0: # feasible! + total_dist = get_total_distance(K, dist_mat, shop_pem, dlv_pem) + return Bundle(all_orders, rider, list(shop_pem), list(dlv_pem), bundle1.total_volume+bundle2.total_volume, total_dist) + + return None + +# 주어진 bundle의 배달원을 변경하는것이 가능한지 테스트 +# Note: 원래 bindle의 방문 순서가 최적이 아닐수도 있기 때문에 방문 순서 조합을 다시 확인함 +def try_bundle_rider_changing(all_orders, dist_mat, bundle, rider): + if bundle.rider.type != rider.type and bundle.total_volume <= rider.capa: + orders = bundle.shop_seq + for shop_pem in permutations(orders): + for dlv_pem in permutations(orders): + feasibility_check = test_route_feasibility(all_orders, rider, shop_pem, dlv_pem) + if feasibility_check == 0: # feasible! + # Note: in-place replacing! + bundle.shop_seq = list(shop_pem) + bundle.dlv_seq = list(dlv_pem) + bundle.rider = rider + bundle.total_dist = get_total_distance(len(all_orders), dist_mat, bundle.shop_seq, bundle.dlv_seq) + bundle.update_cost() + return True + + return False + +# 남아 있는 배달원 중에 *변동비*가 더 싼 배달원을 반환 +# 더 싼 배달원이 없으면 None 반환 +def get_cheaper_available_riders(all_riders, rider): + for r in all_riders: + if r.available_number > 0 and r.var_cost < rider.var_cost: + return r + + return None + +# 주어진 bundle list에서 임의로 두 개를 반환(중복없이) +def select_two_bundles(all_bundles): + bundle1, bundle2 = random.sample(all_bundles, 2) + return bundle1, bundle2 + +# 평균 비용(목적함수) 계산 +# = 총 비용 / 주문 수 +def get_avg_cost(all_orders, all_bundles): + return sum([bundle.cost for bundle in all_bundles]) / len(all_orders) + +# 주어진 bundle list에서 제출용 solution 포멧으로 반환 +def create_solution(prob_name, bundles): + sol = { + 'bundles' : [ + # rider type, shop_seq, dlv_seq + [bundle.rider.type, bundle.shop_seq, bundle.dlv_seq] + for bundle in bundles + ] + } + return sol + + + +# 주어진 solution의 feasibility를 테스트 +# Note: solution은 [배달원유형, pickup 순서, 배달 순서]의 list +# 반환하는 dict에는 solution이 feasible일 경우에는 평균 비용등의 정보가 추가적으로 포함됨 +# solution이 infeasible일 경우에는 그 이유가 'infeasibility' 항목(key)으로 반환 +def solution_check(K, all_orders, all_riders, dist_mat, solution): + + + total_cost = 0 + total_dist = 0 + + infeasibility = None + + if isinstance(solution, list): + + used_riders = { + 'CAR': 0, + 'WALK': 0, + 'BIKE': 0 + } + + all_deliveies = [] + + for bundle_info in solution: + if not isinstance(bundle_info, list) or len(bundle_info) != 3: + infeasibility = f'A bundle information must be a list of rider type, shop_seq, and dlv_seq! ===> {bundle_info}' + break + + rider_type = bundle_info[0] + shop_seq = bundle_info[1] + dlv_seq = bundle_info[2] + + # rider type check + if not rider_type in ['BIKE', 'WALK', 'CAR']: + infeasibility = f'Rider type must be either of BIKE, WALK, or CAR! ===> {rider_type}' + break + + # Get rider object + rider = None + for r in all_riders: + if r.type == rider_type: + rider = r + + # Increase used rider by 1 + used_riders[rider_type] += 1 + + # Pickup sequence check + if not isinstance(shop_seq, list): + infeasibility = f'The second bundle infomation must be a list of pickups! ===> {shop_seq}' + break + + for k in shop_seq: + if not isinstance(k, int) or k<0 or k>=K: + infeasibility = f'Pickup sequence has invalid order number: {k}' + break + + # Delivery sequence check + if not isinstance(dlv_seq, list): + infeasibility = f'The third bundle infomation must be a list of deliveries! ===> {dlv_seq}' + break + + for k in dlv_seq: + if not isinstance(k, int) or k<0 or k>=K: + infeasibility = f'Delivery sequence has invalid order number: {k}' + break + + # Pickup == delivery check + if set(shop_seq) != set(dlv_seq): + infeasibility = f'Sets of pickups and deliveries must be identical: {set(shop_seq)} != {set(dlv_seq)}' + break + + # Volume check + total_volume = get_total_volume(all_orders, shop_seq) + if total_volume > rider.capa: + infeasibility = f"Bundle's total volume exceeds the rider's capacity!: {total_volume} > {rider.capa}" + break + + # Deadline chaeck + pickup_times, dlv_times = get_pd_times(all_orders, rider.T, shop_seq, dlv_seq) + for k in dlv_seq: + all_deliveies.append(k) + if dlv_times[k] > all_orders[k].deadline: + infeasibility = f'Order {k} deadline is violated!: {dlv_times[k]} > {all_orders[k].deadline}' + break + + dist = get_total_distance(K, dist_mat, shop_seq, dlv_seq) + cost = rider.calculate_cost(dist) + + total_dist += dist + total_cost += cost + + if infeasibility is not None: + break + + + if infeasibility is None: + # Check used number of riders + for r in all_riders: + if r.available_number < used_riders[r.type]: + infeasibility = f'The number of used riders of type {r.type} exceeds the given available limit!' + break + + # Check deliveries + for k in range(K): + count = 0 + for k_sol in all_deliveies: + if k == k_sol: + count += 1 + + if count > 1: + infeasibility = f'Order {k} is assigned more than once! ===> {count} > 1' + break + elif count == 0: + infeasibility = f'Order {k} is NOT assigned!' + break + + else: + infeasibility = 'Solution must be a list of bundle information!' + + + if infeasibility is None: # All checks are passed! + checked_solution = { + 'total_cost': float(total_cost), + 'avg_cost': float(total_cost / K), + 'num_drivers': len(solution), + 'total_dist': int(total_dist), + 'feasible': True, + 'infeasibility': None, + 'bundles': solution + } + else: + print(infeasibility) + checked_solution = { + 'feasible': False, + 'infeasibility': infeasibility, + 'bundles': solution + } + + + return checked_solution + +# 주어진 solution의 경로를 visualize +def draw_route_solution(all_orders, solution=None): + + plt.subplots(figsize=(8, 8)) + node_size = 5 + + shop_x = [order.shop_lon for order in all_orders] + shop_y = [order.shop_lat for order in all_orders] + plt.scatter(shop_x, shop_y, c='red', s=node_size, label='SHOPS') + + dlv_x = [order.dlv_lon for order in all_orders] + dlv_y = [order.dlv_lat for order in all_orders] + plt.scatter(dlv_x, dlv_y, c='blue', s=node_size, label='DLVS') + + + if solution is not None: + + rider_idx = { + 'BIKE': 0, + 'CAR': 0, + 'WALK': 0 + } + + for bundle_info in solution['bundles']: + rider_type = bundle_info[0] + shop_seq = bundle_info[1] + dlv_seq = bundle_info[2] + + rider_idx[rider_type] += 1 + + route_color = 'gray' + if rider_type == 'BIKE': + route_color = 'green' + elif rider_type == 'WALK': + route_color = 'orange' + + route_x = [] + route_y = [] + for i in shop_seq: + route_x.append(all_orders[i].shop_lon) + route_y.append(all_orders[i].shop_lat) + + for i in dlv_seq: + route_x.append(all_orders[i].dlv_lon) + route_y.append(all_orders[i].dlv_lat) + + plt.plot(route_x, route_y, c=route_color, linewidth=0.5) + + plt.legend() + +# 주어진 soliution의 묶음 배송 방문 시간대를 visualize +def draw_bundle_solution(all_orders, all_riders, dist_mat, solution): + + plt.subplots(figsize=(6, len(solution['bundles']))) + + x_max = max([ord.deadline for ord in all_orders]) + + bundle_gap = 0.3 + y = 0.2 + + plt.yticks([]) + + for idx, bundle_info in enumerate(solution['bundles']): + rider_type = bundle_info[0] + shop_seq = bundle_info[1] + dlv_seq = bundle_info[2] + + rider = None + for r in all_riders: + if r.type == rider_type: + rider = r + + y_delta = 0.2 + + pickup_times, dlv_times = get_pd_times(all_orders, rider.T, shop_seq, dlv_seq) + + total_volume = 0 + for k in shop_seq: + total_volume += all_orders[k].volume # order volume + plt.hlines(y+y_delta/2, all_orders[k].ready_time, all_orders[k].deadline, colors='gray') + plt.vlines(all_orders[k].ready_time, y, y+y_delta, colors='gray') + plt.vlines(all_orders[k].deadline, y, y+y_delta, colors='gray') + + if total_volume > rider.capa: + plt.scatter(pickup_times[k], y+y_delta/2, c='red', zorder=100, marker='^', edgecolors='red', linewidth=0.5) + else: + plt.scatter(pickup_times[k], y+y_delta/2, c='green', zorder=100) + + if dlv_times[k] > all_orders[k].deadline: + plt.scatter(dlv_times[k], y+y_delta/2, c='red', zorder=100, marker='*', edgecolors='red', linewidth=0.5) + else: + plt.scatter(dlv_times[k], y+y_delta/2, c='orange', zorder=100) + + plt.text(all_orders[k].ready_time, y+y_delta/2, f'{all_orders[k].ready_time} ', ha='right', va='center', c='white', fontsize=6) + plt.text(all_orders[k].deadline, y+y_delta/2, f' {all_orders[k].deadline}', ha='left', va='center', c='white', fontsize=6) + + y += y_delta + + dist = get_total_distance(len(all_orders), dist_mat, shop_seq, dlv_seq) + cost = rider.calculate_cost(dist) + + plt.text(0, y+y_delta, f'{rider_type}: {shop_seq}-{dlv_seq}, tot_cost={cost}, tot_dist={dist}', ha='left', va='top', c='gray', fontsize=8) + y += bundle_gap + plt.hlines(y, 0, x_max, colors='gray', linestyles='dotted') + y += y_delta/2 + + + plt.ylim(0, y) + + From 28de365eb15ef4da55f99040cbbee95598e32304 Mon Sep 17 00:00:00 2001 From: Hoyeol Yoon <9911hoho@gmail.com> Date: Tue, 6 Aug 2024 21:53:24 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=EA=B1=B4=EC=9A=B0=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EC=97=90=20=EC=A4=91=EC=A0=90=20=EC=95=84=EC=9D=B4=EB=94=94?= =?UTF-8?q?=EC=96=B4=20=ED=99=9C=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Mix_gw_hy_240806/Mix_gw_hy_240806.zip | Bin 0 -> 12556 bytes Mix_gw_hy_240806/custom_util.py | 432 +++++++++++++++++++++++ Mix_gw_hy_240806/myalgorithm.py | 263 ++++++++++++++ Mix_gw_hy_240806/util.py | 470 ++++++++++++++++++++++++++ 4 files changed, 1165 insertions(+) create mode 100644 Mix_gw_hy_240806/Mix_gw_hy_240806.zip create mode 100644 Mix_gw_hy_240806/custom_util.py create mode 100644 Mix_gw_hy_240806/myalgorithm.py create mode 100644 Mix_gw_hy_240806/util.py diff --git a/Mix_gw_hy_240806/Mix_gw_hy_240806.zip b/Mix_gw_hy_240806/Mix_gw_hy_240806.zip new file mode 100644 index 0000000000000000000000000000000000000000..5a3436fbd1cabbf5d7788f39ae087648224b8f92 GIT binary patch literal 12556 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 literal 0 HcmV?d00001 diff --git a/Mix_gw_hy_240806/custom_util.py b/Mix_gw_hy_240806/custom_util.py new file mode 100644 index 0000000..fc759c1 --- /dev/null +++ b/Mix_gw_hy_240806/custom_util.py @@ -0,0 +1,432 @@ +from util import * +import math +import itertools +from itertools import permutations, combinations + +# 2024-08-03 : all_partitions, find_nearest_bundles, custom_try_merging_multiple_bundles_by_distance, simulated_annealing 함수 추가 +# 2024-08-06 : find_nearest_triples, get_infeasible_pairs 함수 추가 + +def get_infeasible_pairs(all_orders, dist_mat, all_riders): + infeasible_pairs = set() + num_orders = len(all_orders) + + for i in range(num_orders): + for j in range(i + 1, num_orders): + if i != j: + order_i = all_orders[i] + order_j = all_orders[j] + feasible = False # Assume infeasible until proven otherwise + + for rider in all_riders: + if get_total_volume(all_orders, [i, j]) <= rider.capa: + for shop_seq in itertools.permutations([i, j]): + for dlv_seq in itertools.permutations([i, j]): + feasibility = test_route_feasibility(all_orders, rider, shop_seq, dlv_seq) + if feasibility == 0: + feasible = True # Found a feasible combination + break + if feasible: + break + if feasible: + break + + if not feasible: + infeasible_pairs.add((i, j)) + infeasible_pairs.add((j, i)) + + return infeasible_pairs + +def find_nearest_triples(dist_mat, all_bundles): + bundle_triples = [] + for i in range(len(all_bundles)): + for j in range(i + 1, len(all_bundles)): + for k in range(j + 1, len(all_bundles)): + if i!=j and j!=k and i!= k : + bundle1 = all_bundles[i] + bundle2 = all_bundles[j] + bundle3 = all_bundles[k] + min_dist = min( + dist_mat[bundle1.shop_seq[-1]][bundle2.shop_seq[0]], + dist_mat[bundle2.shop_seq[-1]][bundle3.shop_seq[0]], + dist_mat[bundle3.shop_seq[-1]][bundle1.shop_seq[0]] + ) + bundle_triples.append((min_dist, bundle1, bundle2, bundle3)) + bundle_triples = sorted(bundle_triples, key=lambda x: x[0]) + return bundle_triples + +def find_nearest_triples_with_middle(all_orders, all_bundles): + bundle_triples = [] + + all_bundles_avg_loc = avg_loc(all_orders, all_bundles) + all_bundles_dist_mat = dist_mat_by_loc(all_bundles_avg_loc) + N = len(all_bundles) + + for i in range(len(all_bundles)): + for j in range(i + 1, len(all_bundles)): + for k in range(j + 1, len(all_bundles)): + if i!=j and j!=k and i!= k : + dist_ij = all_bundles_dist_mat[i, j] + all_bundles_dist_mat[i+N, j+N] + (all_bundles_dist_mat[i, i+N] + all_bundles_dist_mat[i, j+N] + all_bundles_dist_mat[i+N, j] + all_bundles_dist_mat[j, j+N])*0.25 + dist_jk = all_bundles_dist_mat[j, k] + all_bundles_dist_mat[j+N, k+N] + (all_bundles_dist_mat[j, j+N] + all_bundles_dist_mat[j, k+N] + all_bundles_dist_mat[j+N, k] + all_bundles_dist_mat[k, k+N])*0.25 + dist_ki = all_bundles_dist_mat[k, i] + all_bundles_dist_mat[k+N, i+N] + (all_bundles_dist_mat[k, k+N] + all_bundles_dist_mat[k, i+N] + all_bundles_dist_mat[k+N, i] + all_bundles_dist_mat[i, i+N])*0.25 + total_dist = dist_ij + dist_jk + dist_ki + + bundle1 = all_bundles[i] + bundle2 = all_bundles[j] + bundle3 = all_bundles[k] + + bundle_triples.append((total_dist, bundle1, bundle2, bundle3)) + bundle_triples = sorted(bundle_triples, key=lambda x: x[0]) + return bundle_triples + +def draw_route_bundles(all_orders, all_bundles): + plt.subplots(figsize=(12, 12)) + node_size = 5 + + shop_x = [order.shop_lon for order in all_orders] + shop_y = [order.shop_lat for order in all_orders] + plt.scatter(shop_x, shop_y, c='red', s=node_size, label='SHOPS') + + dlv_x = [order.dlv_lon for order in all_orders] + dlv_y = [order.dlv_lat for order in all_orders] + plt.scatter(dlv_x, dlv_y, c='blue', s=node_size, label='DLVS') + + rider_idx = { + 'BIKE': 0, + 'CAR': 0, + 'WALK': 0 + } + + for bundle in all_bundles: + rider_type = bundle.rider.type + shop_seq = bundle.shop_seq + dlv_seq = bundle.dlv_seq + + rider_idx[rider_type] += 1 + + route_color = 'gray' + if rider_type == 'BIKE': + route_color = 'green' + elif rider_type == 'WALK': + route_color = 'orange' + + route_x = [] + route_y = [] + for i in shop_seq: + route_x.append(all_orders[i].shop_lon) + route_y.append(all_orders[i].shop_lat) + + for i in dlv_seq: + route_x.append(all_orders[i].dlv_lon) + route_y.append(all_orders[i].dlv_lat) + + plt.plot(route_x, route_y, c=route_color, linewidth=0.5) + + plt.legend() + plt.show() + +def count_bundles(all_bundles): + counts = { + 'WALK': {'total': 0, 'lengths': {}}, + 'BIKE': {'total': 0, 'lengths': {}}, + 'CAR': {'total': 0, 'lengths': {}} + } + + # 각 요소를 순회하며 카운팅 + for bundle in all_bundles: + transport_type = bundle.rider.type + counts[transport_type]['total'] += 1 + + length = len(bundle.shop_seq) # `shop_seq`의 길이를 기준으로 한다. + if length not in counts[transport_type]['lengths']: + counts[transport_type]['lengths'][length] = 0 + counts[transport_type]['lengths'][length] += 1 + + # 결과 출력 + for transport_type, data in counts.items(): + total_count = data['total'] + print(f"{transport_type}: 총 {total_count}개") + for length, count in sorted(data['lengths'].items()): + print(f" 길이 {length}: {count}개") + +def find_nearest_bundles(dist_mat, all_bundles): + nearest_pairs = [] + for i, bundle1 in enumerate(all_bundles): + min_dist = float('inf') + nearest_bundle = None + for j, bundle2 in enumerate(all_bundles): + if i != j: + dist = dist_mat[bundle1.shop_seq[-1], bundle2.shop_seq[0]] #단순하게 bundle1의 pickup 지점의 끝과, bundle2의 pickup 지점의 시작 사이의 거리를 + if dist < min_dist: + min_dist = dist + nearest_bundle = bundle2 + if nearest_bundle: + nearest_pairs.append((min_dist, bundle1, nearest_bundle)) + nearest_pairs = sorted(nearest_pairs, key=lambda x: x[0]) + #print(f'Nearest pairs: {nearest_pairs}') # 디버깅 출력 + return nearest_pairs + +def all_partitions(orders): + if len(orders) == 1: + yield [orders] + return + + first = orders[0] + for smaller in all_partitions(orders[1:]): + # insert `first` in each of the subpartition's subsets + for n, subset in enumerate(smaller): + yield smaller[:n] + [[first] + subset] + smaller[n + 1:] + # put `first` in its own subset + yield [[first]] + smaller + +def filter_partitions(orders, num_parts): + partitions = list(all_partitions(orders)) + return [p for p in partitions if len(p) == num_parts] + +def evaluate_bundles(bundles): + total_cost = sum(bundle.cost for bundle in bundles) + return total_cost + +def custom_try_merging_multiple_bundles_by_distance(K, dist_mat, all_orders, bundles, all_riders, infeasible_pairs): + merged_orders = list(set([order for bundle in bundles for order in bundle.shop_seq])) # 중복 주문 제거 + total_volume = get_total_volume(all_orders, merged_orders) + best_bundles = [] + min_total_cost = float('inf') + + def evaluate_bundles(bundles): + total_cost = sum(bundle.cost for bundle in bundles) + return total_cost + + def heuristic_merge(orders, rider): + # Heuristic approach to find a feasible sequence + shop_seq = [orders[0]] + dlv_seq = [orders[0]] + remaining_orders = set(orders[1:]) + while remaining_orders: + last_shop = shop_seq[-1] + nearest_order = min(remaining_orders, key=lambda x: dist_mat[last_shop][x]) + shop_seq.append(nearest_order) + dlv_seq.append(nearest_order) + remaining_orders.remove(nearest_order) + return shop_seq, dlv_seq + + # Try to merge all orders into one bundle + if len(merged_orders) <= 5: + for rider in all_riders: + if rider.available_number > 0 and total_volume <= rider.capa: + for shop_pem in itertools.permutations(merged_orders): + if any((shop_pem[i], shop_pem[j]) in infeasible_pairs for i in range(len(shop_pem)) for j in range(i + 1, len(shop_pem))): + continue + for dlv_pem in itertools.permutations(merged_orders): + feasibility_check = test_route_feasibility(all_orders, rider, shop_pem, dlv_pem) + if feasibility_check == 0: + total_dist = get_total_distance(K, dist_mat, shop_pem, dlv_pem) + temp_bundle = Bundle(all_orders, rider, list(shop_pem), list(dlv_pem), total_volume, total_dist) + temp_bundle.update_cost() + current_cost = evaluate_bundles([temp_bundle]) + if current_cost < min_total_cost: + min_total_cost = current_cost + best_bundles = [temp_bundle] + #print(f'one bundle : {best_bundles}') + + # Try to split into two bundles + if len(merged_orders) > 1: + two_partitions = filter_partitions(merged_orders, 2) + for partition in two_partitions: + if any((i, j) in infeasible_pairs for part in partition for i in part for j in part if i != j): + continue + candidate_bundles = [] + valid_partition = True + for part in partition: + part_volume = get_total_volume(all_orders, part) + part_bundle = None + for rider in all_riders: + if rider.available_number > 1 and part_volume <= rider.capa: + for shop_pem in itertools.permutations(part): + if any((shop_pem[i], shop_pem[j]) in infeasible_pairs for i in range(len(shop_pem)) for j in range(i + 1, len(shop_pem))): + continue + for dlv_pem in itertools.permutations(part): + feasibility_check = test_route_feasibility(all_orders, rider, shop_pem, dlv_pem) + if feasibility_check == 0: + total_dist = get_total_distance(K, dist_mat, shop_pem, dlv_pem) + part_bundle = Bundle(all_orders, rider, list(shop_pem), list(dlv_pem), part_volume, total_dist) + part_bundle.update_cost() + candidate_bundles.append(part_bundle) + break + if part_bundle: + break + if part_bundle: + break + if not part_bundle: + valid_partition = False + break + + if valid_partition and len(candidate_bundles) == 2: + current_cost = evaluate_bundles(candidate_bundles) + if current_cost < min_total_cost: + min_total_cost = current_cost + best_bundles = candidate_bundles + #print(f'two bundles : {best_bundles}') + + # Try to split into three bundles + if len(merged_orders) > 2: + three_partitions = filter_partitions(merged_orders, 3) + for partition in three_partitions: + if any((i, j) in infeasible_pairs for part in partition for i in part for j in part if i != j): + continue + candidate_bundles = [] + valid_partition = True + for part in partition: + part_volume = get_total_volume(all_orders, part) + part_bundle = None + for rider in all_riders: + if rider.available_number > 2 and part_volume <= rider.capa: + for shop_pem in itertools.permutations(part): + if any((shop_pem[i], shop_pem[j]) in infeasible_pairs for i in range(len(shop_pem)) for j in range(i + 1, len(shop_pem))): + continue + for dlv_pem in itertools.permutations(part): + feasibility_check = test_route_feasibility(all_orders, rider, shop_pem, dlv_pem) + if feasibility_check == 0: + total_dist = get_total_distance(K, dist_mat, shop_pem, dlv_pem) + part_bundle = Bundle(all_orders, rider, list(shop_pem), list(dlv_pem), part_volume, total_dist) + part_bundle.update_cost() + candidate_bundles.append(part_bundle) + break + if part_bundle: + break + if part_bundle: + break + if not part_bundle: + valid_partition = False + break + + if valid_partition and len(candidate_bundles) == 3: + current_cost = evaluate_bundles(candidate_bundles) + if current_cost < min_total_cost: + min_total_cost = current_cost + best_bundles = candidate_bundles + #print(f'three bundles : {best_bundles}') + + return best_bundles + +# Simulated Annealing을 사용하여 번들을 최적화하는 함수 +def simulated_annealing(all_bundles, K, dist_mat, all_orders, all_riders, timelimit=60, initial_temp=100, cooling_rate=0.99): + current_solution = all_bundles # 현재 해를 초기 해로 설정 + current_cost = sum(bundle.cost for bundle in all_bundles) / K # 초기 해의 비용 계산 + best_solution = current_solution[:] # 초기 해를 최적 해로 설정 + best_cost = current_cost # 초기 해의 비용을 최적 비용으로 설정 + temperature = initial_temp # 초기 온도 설정 + + if len(current_solution) > 1: # 번들이 2개 이상일 때만 수행 + i, j = random.sample(range(len(current_solution)), 2) # 무작위로 두 번들을 선택 + selected_bundles = [current_solution[i], current_solution[j]] + new_bundles = custom_try_merging_multiple_bundles_by_distance(K, dist_mat, all_orders, selected_bundles, all_riders) + + if new_bundles: # 새로운 번들이 생성되었을 때 + new_cost = sum(bundle.cost for bundle in new_bundles) / K + + # 새로운 비용이 현재 비용보다 적거나, 확률적으로 수락할 때 + if new_cost < current_cost or random.uniform(0, 1) < math.exp((current_cost - new_cost) / temperature): + current_solution = [bundle for k, bundle in enumerate(current_solution) if k != i and k != j] + new_bundles + current_cost = new_cost + + if current_cost < best_cost: # 새로운 해가 최적 해보다 나을 때 + best_solution = current_solution[:] + best_cost = current_cost + + temperature *= cooling_rate # 온도 감소 + + return best_solution, best_cost # 최적 해와 최적 비용 반환 + +def custom_try_bundle_rider_changing(all_orders, dist_mat, bundle, all_riders): + old_rider = bundle.rider + best_shop_seq = None + best_dlv_seq = None + best_rider = None + min_total_cost = float('inf') + + for rider in all_riders: + if bundle.total_volume <= rider.capa: + orders = bundle.shop_seq + + for shop_pem in permutations(orders): + for dlv_pem in permutations(orders): + feasibility_check = test_route_feasibility(all_orders, rider, shop_pem, dlv_pem) + if feasibility_check == 0: # feasible! + total_dist = get_total_distance(len(all_orders), dist_mat, shop_pem, dlv_pem) + bundle.shop_seq = list(shop_pem) + bundle.dlv_seq = list(dlv_pem) + bundle.rider = rider + bundle.total_dist = total_dist + bundle.update_cost() + if bundle.cost < min_total_cost: + min_total_cost = bundle.cost + best_shop_seq = list(shop_pem) + best_dlv_seq = list(dlv_pem) + best_rider = rider + + if best_shop_seq and best_dlv_seq and best_rider: + # Note: in-place replacing! + bundle.shop_seq = best_shop_seq + bundle.dlv_seq = best_dlv_seq + bundle.rider = best_rider + bundle.total_dist = get_total_distance(len(all_orders), dist_mat, best_shop_seq, best_dlv_seq) + bundle.update_cost() # update the cost with the best sequences and rider + if old_rider != best_rider : + old_rider.available_number += 1 + best_rider.available_number -= 1 + return True + + return False + +def avg_loc(all_orders, all_bundles): + bundles_index = [bundle.shop_seq for bundle in all_bundles] + bundles_avg_loc = [] + for index_seq in bundles_index: + ords_loc = [((order.shop_lat, order.shop_lon), (order.dlv_lat, order.dlv_lon)) for order in all_orders if order.id in index_seq] + bundle_loc = np.zeros((2,2)) + for shop_loc, dlv_loc in ords_loc: + bundle_loc[0] += np.array(shop_loc) + bundle_loc[1] += np.array(dlv_loc) + bundle_loc /= len(ords_loc) + bundles_avg_loc.append(bundle_loc) + + return bundles_avg_loc + +def haversine_distance(coord1, coord2): + lat1, lon1 = coord1 + lat2, lon2 = coord2 + R = 6371.0 # 지구의 반지름 (킬로미터) + + phi1 = math.radians(lat1) + phi2 = math.radians(lat2) + delta_phi = math.radians(lat2 - lat1) + delta_lambda = math.radians(lon2 - lon1) + + a = math.sin(delta_phi / 2.0)**2 + \ + math.cos(phi1) * math.cos(phi2) * \ + math.sin(delta_lambda / 2.0)**2 + c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)) + + distance = R * c + return distance + +def dist_mat_by_loc(all_bundles_avg_loc): + + N = len(all_bundles_avg_loc) + bundles_dist_mat = np.zeros((2 * N, 2 * N)) + + for i in range(N): + for j in range(N): + # 픽업 지점 간 거리 + bundles_dist_mat[i][j] = haversine_distance(all_bundles_avg_loc[i][0], all_bundles_avg_loc[j][0]) + + # 배송 지점과 픽업 지점 간 거리 + bundles_dist_mat[i + N][j] = haversine_distance(all_bundles_avg_loc[i][1], all_bundles_avg_loc[j][0]) + + # 픽업 지점과 배송 지점 간 거리 + bundles_dist_mat[i][j + N] = haversine_distance(all_bundles_avg_loc[i][0], all_bundles_avg_loc[j][1]) + + # 배송 지점 간 거리 + bundles_dist_mat[i + N][j + N] = haversine_distance(all_bundles_avg_loc[i][1], all_bundles_avg_loc[j][1]) + + return bundles_dist_mat \ No newline at end of file diff --git a/Mix_gw_hy_240806/myalgorithm.py b/Mix_gw_hy_240806/myalgorithm.py new file mode 100644 index 0000000..748a6d1 --- /dev/null +++ b/Mix_gw_hy_240806/myalgorithm.py @@ -0,0 +1,263 @@ +import heapq +from util import * +from custom_util import * + +def algorithm(K, all_orders, all_riders, dist_mat, timelimit=60): + + start_time = time.time() + + print('Code Start') + print('---------------------------------------------------------------------------------------') + + for r in all_riders: + r.T = np.round(dist_mat / r.speed + r.service_time) + + # A solution is a list of bundles + solution = [] + + #------------- Custom algorithm code starts from here --------------# + + walk_rider = None + for r in all_riders: + if r.type == 'WALK': + walk_rider = r + + car_rider = None + for r in all_riders: + if r.type == 'CAR': + car_rider = r + + all_bundles = [] + all_orders_tmp = all_orders.copy() + infeasible_pairs = get_infeasible_pairs(all_orders, dist_mat, all_riders) + print(f'num of infeasible pairs : {len(infeasible_pairs)}') + heap = [] + cant_walk_list = [] + filt_ord = [] + + for rider in all_riders: + if rider.type == "WALK": + walk_speed = rider.speed #도보 속도 + walk_time_mat = np.round(dist_mat/rider.speed + rider.service_time) #도보 이동시간 + break + + for order in all_orders_tmp: + ready_time = order.order_time + order.cook_time + time_diff = order.deadline - ready_time #해당 order의 준비~데드라인의 시간 차이 + walk_time = walk_time_mat[order.id][order.id+K] #해당 order를 배송하기 위해 도보로 이동할 떄 필요한 시간 + + if time_diff < walk_time: #만약 주어진 시간이 모자라면 + cant_walk_list.append(order.id) #배달 불가능한 배달 번호 추가 + + fcut_orders = [order for order in all_orders_tmp if order.id not in cant_walk_list] #불가능한 orders를 첫번째 잘라내고 남은 orders + + for f_order in fcut_orders: + cant_merge_list = [f_order.id] + for s_order in fcut_orders: + #첫번째 order의 데드라인보다 두번째 order의 레디가 더 늦은 경우 cut + if f_order.deadline < s_order.order_time + s_order.cook_time or f_order.order_time + f_order.cook_time > s_order.deadline: + cant_merge_list.append(s_order.id) + # 두번째 order의 레디에 2픽업->1도착의 이동 시간을 더해서 첫번째 order의 데드라인보다 늦으면 cut + elif s_order.order_time + s_order.cook_time + walk_time_mat[s_order.id][f_order.id+K] > f_order.deadline: + cant_merge_list.append(s_order.id) + #print(f"{f_order.id}번째 order는 {cant_merge_list}와 결합 불가능") + scut_orders = [order for order in fcut_orders if order.id not in cant_merge_list] #불가능한 orders를 두번째 잘라내고 남은 orders + + #Cut 이후에 merging 작업 진행 + ord = f_order + new_bundle = Bundle(all_orders, walk_rider, [ord.id], [ord.id], ord.volume, dist_mat[ord.id, ord.id + K]) + + for s_ord in scut_orders: + new_bundle.shop_seq.append(s_ord.id) + new_bundle.dlv_seq.append(s_ord.id) + + for dlv_pem in permutations(new_bundle.dlv_seq): + feasibility_check = test_route_feasibility(all_orders, walk_rider, new_bundle.shop_seq, dlv_pem) + if feasibility_check == 0: # feasible! + cost_1 = walk_rider.calculate_cost(dist_mat[ord.id, ord.id + K]) + cost_2 = walk_rider.calculate_cost(dist_mat[s_ord.id, s_ord.id + K]) + fea_bundle = Bundle(all_orders, walk_rider, new_bundle.shop_seq[:], list(dlv_pem), + new_bundle.total_volume + s_ord.volume, get_total_distance(K, dist_mat, new_bundle.shop_seq, dlv_pem)) + fea_bundle.update_cost() + cost_new = fea_bundle.cost + cost_diff = cost_1 + cost_2 - cost_new + heapq.heappush(heap, [-cost_diff, fea_bundle.shop_seq, fea_bundle.dlv_seq, fea_bundle.total_volume, fea_bundle.total_dist]) + + new_bundle.shop_seq.pop() + new_bundle.dlv_seq.pop() + + while heap: + smallest = heapq.heappop(heap) + if all(item not in filt_ord for item in smallest[1]): + filt_ord.extend(smallest[1]) + good_bundle = Bundle(all_orders, walk_rider, smallest[1], smallest[2], smallest[3], smallest[4]) + all_bundles.append(good_bundle) + walk_rider.available_number -= 1 + + # Update all_orders_tmp + all_orders_tmp = [order for order in all_orders_tmp if order.id not in filt_ord] + time_1 = time.time() + count_bundles(all_bundles) + print('---------------------------------------------------------------------------------------') + # draw_route_bundles(all_orders, all_bundles) + # Create initial bundles using a greedy approach based on distance + + while all_orders_tmp: + ord = all_orders_tmp.pop(0) + # 일단 car_rider를 넣어 feasible한 bundle을 찾음 + new_bundle = Bundle(all_orders, car_rider, [ord.id], [ord.id], ord.volume, dist_mat[ord.id, ord.id + K]) + # Try to add the nearest orders to the current bundle + while True: + nearest_order = None + min_dist = float('inf') + for other_ord in all_orders_tmp: + dist = dist_mat[ord.id, other_ord.id] + dist_mat[ord.id + K, other_ord.id + K] + (dist_mat[ord.id, ord.id + K] + dist_mat[ord.id, other_ord.id + K] + dist_mat[ord.id + K, other_ord.id] + dist_mat[other_ord.id, other_ord.id + K])*0.25 + if dist < min_dist and new_bundle.total_volume + other_ord.volume <= car_rider.capa: + min_dist = dist + nearest_order = other_ord + + if nearest_order: + new_bundle.shop_seq.append(nearest_order.id) + new_bundle.dlv_seq.append(nearest_order.id) + new_bundle.total_volume += nearest_order.volume + new_bundle.total_dist += min_dist + + feasibility_check = test_route_feasibility(all_orders, car_rider, new_bundle.shop_seq, new_bundle.dlv_seq) + if feasibility_check == 0: # Feasible + car_rider.available_number -= 1 + all_orders_tmp.remove(nearest_order) + new_bundle.update_cost() + custom_try_bundle_rider_changing(all_orders, dist_mat, new_bundle, all_riders) + else: + # Remove last added order if not feasible + new_bundle.shop_seq.pop() + new_bundle.dlv_seq.pop() + new_bundle.total_volume -= nearest_order.volume + new_bundle.total_dist -= min_dist + break + + else: + break + + all_bundles.append(new_bundle) + best_obj = sum((bundle.cost for bundle in all_bundles)) / K + print(f'Initial best obj = {best_obj}') + #print(all_bundles) + count_bundles(all_bundles) + #draw_route_bundles(all_orders, all_bundles) + time_2 = time.time() + print(f'Elapsed time for initializing: {time_2 - time_1}') + print('---------------------------------------------------------------------------------------') + + #1개짜리인 bundle을 모아서 따로 리스트에 저장 + #각 bundle 별로 merge + cant_merge_list = [] + + while True : + no_walk_bundles = [bundle for bundle in all_bundles if bundle.rider.type != 'WALK'] + single_order_bundles = [(-bundle.cost, bundle) for bundle in all_bundles if len(bundle.shop_seq) == 1 and bundle.shop_seq[0] not in cant_merge_list] + if len(single_order_bundles) <= 3 : + break + heapq.heapify(single_order_bundles) + best_improvement = 0 + best_candidate = None + best_new_bundles = None + _, single_bundle = heapq.heappop(single_order_bundles) + #print(f'single_bundle : {single_bundle}') + Flag = False + for i in range(len(no_walk_bundles)): + if no_walk_bundles[i] != single_bundle : + candidate_bundles = [single_bundle, no_walk_bundles[i]] + #print(f'candidate_bundles : {candidate_bundles}') + new_bundles = custom_try_merging_multiple_bundles_by_distance(K, dist_mat, all_orders, candidate_bundles, all_riders, infeasible_pairs) + #print(f'new bundles : {new_bundles}') + if new_bundles: + #print(new_bundles) + current_cost = sum(bundle.cost for bundle in candidate_bundles) + new_cost = sum(bundle.cost for bundle in new_bundles) + improvement = current_cost - new_cost + #print(f'improvement : {improvement}') + if improvement > best_improvement: + best_improvement = improvement + best_candidate = candidate_bundles + best_new_bundles = new_bundles + Flag = True + if not Flag : + cant_merge_list.append(single_bundle.shop_seq[0]) + + if best_new_bundles: + #print(f'best_new_bundles : {best_new_bundles}') + #print(f'improvement : {best_improvement}') + for tmp_bundle in best_candidate : + #print(tmp_bundle) + tmp_bundle.rider.available_number += 1 + all_bundles.remove(tmp_bundle) + for tmp_bundle in best_new_bundles : + tmp_bundle.rider.available_number -= 1 + all_bundles.extend(best_new_bundles) + #count_bundles(all_bundles) + + cur_obj = sum((bundle.cost for bundle in all_bundles)) / K + if cur_obj < best_obj: + best_obj = cur_obj + print(f'Current best obj = {best_obj}') + count_bundles(all_bundles) + time_3 = time.time() + print(f'Elapsed time for merging single bundles: {time_3 - time_2}') + #draw_route_bundles(all_orders, all_bundles) + #print(all_bundles) + print('---------------------------------------------------------------------------------------') + + #--------------- + iter = 0 + while time.time() - start_time < timelimit and len(all_bundles) > 1: + no_walk_bundles = [bundle for bundle in all_bundles if bundle.rider.type != 'WALK'] + iter += 1 + num_less_two = len([bundle for bundle in no_walk_bundles if len(bundle.shop_seq) <= 2]) + if num_less_two >= 20: + limited_bundles = [bundle for bundle in no_walk_bundles if len(bundle.shop_seq) <= 2] + else : + limited_bundles = [bundle for bundle in no_walk_bundles if len(bundle.shop_seq) <= 3] + nearest_triples = find_nearest_triples_with_middle(all_orders, limited_bundles) + improved = False + for min_dist, bundle1, bundle2, bundle3 in nearest_triples: + new_bundles = custom_try_merging_multiple_bundles_by_distance(K, dist_mat, all_orders, [bundle1, bundle2, bundle3], all_riders, infeasible_pairs) + if new_bundles: + improvement = sum(bundle.cost for bundle in [bundle1, bundle2, bundle3]) - sum(bundle.cost for bundle in new_bundles) + if improvement > 0: + bundle1.rider.available_number += 1 + bundle2.rider.available_number += 1 + bundle3.rider.available_number += 1 + all_bundles.remove(bundle1) + all_bundles.remove(bundle2) + all_bundles.remove(bundle3) + for new_bundle in new_bundles: + new_bundle.rider.available_number -= 1 + all_bundles.append(new_bundle) + cur_obj = sum((bundle.cost for bundle in all_bundles)) / K + if cur_obj < best_obj: + best_obj = cur_obj + print(f'Current best obj = {best_obj}') + count_bundles(all_bundles) + print(f'Current time: {time.time() - start_time}') + print('---------------------------------------------------------------------------------------') + #draw_route_bundles(all_orders, all_bundles) + improved = True + break + if time.time() - start_time >= timelimit: + break + if not improved: + break + + cur_obj = sum((bundle.cost for bundle in all_bundles)) / K + if cur_obj < best_obj: + best_obj = cur_obj + print(f'Final best obj = {best_obj}') + count_bundles(all_bundles) + solution = [ + [bundle.rider.type, bundle.shop_seq, bundle.dlv_seq] + for bundle in all_bundles + ] + print(solution) + + return solution diff --git a/Mix_gw_hy_240806/util.py b/Mix_gw_hy_240806/util.py new file mode 100644 index 0000000..7707e48 --- /dev/null +++ b/Mix_gw_hy_240806/util.py @@ -0,0 +1,470 @@ + +import json +import numpy as np +from itertools import permutations +import random +import time +import pprint + +import matplotlib.pyplot as plt + + +# Change history +# 2024/7/20 - Fixed a bug in solution_check() to make sure pickups == deliveries +# 2024/7/1 - Update total_dist for a new bundle as well in try_bundle_rider_changing() +# 2024/6/21 - Fixed a comment in Order.__init__() +# 2024/6/16 - Fixed a bug that does not set the bundle routes in try_bundle_rider_changing() +# 2024/5/17 - Fixed a bug in get_pd_times() + + +# 주문 class +class Order: + def __init__(self, order_info): + # [ORD_ID, ORD_TIME, SHOP_LAT, SHOP_LON, DLV_LAT, DLV_LON, COOK_TIME, VOL, DLV_DEADLINE] + self.id = order_info[0] + self.order_time = order_info[1] + self.shop_lat = order_info[2] + self.shop_lon = order_info[3] + self.dlv_lat = order_info[4] + self.dlv_lon = order_info[5] + self.cook_time = order_info[6] + self.volume = order_info[7] + self.deadline = order_info[8] + + self.ready_time = self.order_time + self.cook_time + + def __repr__(self) -> str: + return f'Order([{self.id}, {self.order_time}, {self.shop_lat}, {self.shop_lon}, {self.dlv_lat}, {self.dlv_lon}, {self.volume}, {self.cook_time}, {self.deadline}])' + +# 배달원 class +class Rider: + def __init__(self, rider_info): + # [type, speed, capa, var_cost, fixed_cost, service_time, available number] + self.type = rider_info[0] + self.speed = rider_info[1] + self.capa = rider_info[2] + self.var_cost = rider_info[3] + self.fixed_cost = rider_info[4] + self.service_time = rider_info[5] + self.available_number = rider_info[6] + + def __repr__(self) -> str: + return f'Rider([{self.type}, {self.speed}, {self.capa}, {self.var_cost}, {self.fixed_cost}, {self.service_time}, {self.available_number}])' + + # 주어진 거리에 대한 배달원 비용 계산 + # = 배달원별 고정비 + 이동거리로 계산된 변동비 + def calculate_cost(self, dist): + return self.fixed_cost + dist / 100.0 * self.var_cost + +# 묶음 주문 정보 +class Bundle: + def __init__(self, all_orders, rider, shop_seq, dlv_seq, total_volume, total_dist, feasible=True): + self.rider = rider + self.all_orders = all_orders + self.feasible = feasible + self.shop_seq = shop_seq + self.dlv_seq = dlv_seq + self.total_volume = total_volume + self.total_dist = total_dist + self.update_cost() + + # 묶음 주문의 비용 update + def update_cost(self): + self.cost = self.rider.calculate_cost(self.total_dist) + self.cost_per_ord = self.cost / len(self.shop_seq) + + + def __repr__(self) -> str: + return f'Bundle(all_orders, {self.rider.type}, {self.shop_seq}, {self.dlv_seq}, {self.total_volume}, {self.feasible})' + + +# 주문들의 총 부피 계산 +# shop_seq는 주문들의 pickup list +# Note: shop_seq는 주문 id의 list와 동일 +def get_total_volume(all_orders, shop_seq): + return sum(all_orders[k].volume for k in shop_seq) + +# shop_seq의 순서로 pickup하고 dlv_seq 순서로 배달할 때 총 거리 계산 +# Note: shop_seq 와 dlv_seq는 같은 주문 id들을 가져야 함. 즉, set(shop_seq) == seq(dlv_seq). (주문 id들의 순서는 바뀔 수 있음) +def get_total_distance(K, dist_mat, shop_seq, dlv_seq): + return sum(dist_mat[i,j] for (i,j) in zip(shop_seq[:-1], shop_seq[1:])) + dist_mat[shop_seq[-1], dlv_seq[0]+K] + sum(dist_mat[i+K,j+K] for (i,j) in zip(dlv_seq[:-1], dlv_seq[1:])) + +# shop_seq의 순서로 pickup하고 dlv_seq 순서로 배달할 때 pickup과 delivery시간을 반환 +# Note: shop_seq 와 dlv_seq는 같은 주문 id들을 가져야 함. 즉, set(shop_seq) == seq(dlv_seq). (주문 id들의 순서는 바뀔 수 있음) +def get_pd_times(all_orders, rider, shop_seq, dlv_seq): + + K = len(all_orders) + + pickup_times = {} + + k = shop_seq[0] + t = all_orders[k].order_time + all_orders[k].cook_time # order time + order cook time + pickup_times[k] = t + for next_k in shop_seq[1:]: + t = max(t+rider.T[k, next_k], all_orders[next_k].ready_time) # max{travel time + service time, ready time} + pickup_times[next_k] = t + + k = next_k + + dlv_times = {} + + k = dlv_seq[0] + t += rider.T[shop_seq[-1], k + K] + + dlv_times[k] = t + + for next_k in dlv_seq[1:]: + t += rider.T[k + K, next_k + K] + + dlv_times[next_k] = t + + k = next_k + + return pickup_times, dlv_times + +# shop_seq의 순서로 pickup하고 dlv_seq 순서로 배달원 rider가 배달할 때 묶음주문 제약 만족 여부 테스트 +# 모든 제약을 만족하면 0 반환 +# 용량 제약을 만족하지 못하면 -1 반환 +# 시간 제약을 만족하지 못하면 -2 반환 +# Note: shop_seq 와 dlv_seq는 같은 주문 id들을 가져야 함. 즉, set(shop_seq) == seq(dlv_seq). (주문 id들의 순서는 바뀔 수 있음) +def test_route_feasibility(all_orders, rider, shop_seq, dlv_seq): + + total_vol = get_total_volume(all_orders, shop_seq) + if total_vol > rider.capa: + # Capacity overflow! + return -1 # Capacity infeasibility + + pickup_times, dlv_times = get_pd_times(all_orders, rider, shop_seq, dlv_seq) + + for k, dlv_time in dlv_times.items(): + if dlv_time > all_orders[k].deadline: + return -2 # Deadline infeasibility + + return 0 + +# 두 개의 bundle이 제약을 만족하면서 묶일 수 있는지 테스트 +# 합쳐진 붂음배송 경로는 가능한 모든 pickup/delivery 조합을 확인 +# 두 개의 bundle을 합치는게 가능하면 합쳐진 새로운 bundle을 반환 +# 합치는게 불가능하면 None을 반환 +# Note: 이 때 배달원은 두 개의 주어진 bundle을 배달하는 배달원들만 후보로 테스트(주어진 bundle에 사용되지 않는 배달원을 묶는게 가능할 수 있음!) +# Note: 여러개의 배달원으로 묶는게 가능할 때 가장 먼저 가능한 배달원 기준으로 반환(비용을 고려하지 않음) +def try_merging_bundles(K, dist_mat, all_orders, bundle1, bundle2): + merged_orders = bundle1.shop_seq + bundle2.shop_seq + total_volume = get_total_volume(all_orders, merged_orders) + if bundle1.rider.type == bundle2.rider.type: + riders = [bundle1.rider] + else: + riders = [bundle1.rider, bundle2.rider] + for rider in riders: + # We skip the test if there are too many orders + if total_volume <= rider.capa and len(merged_orders) <= 5: + for shop_pem in permutations(merged_orders): + for dlv_pem in permutations(merged_orders): + feasibility_check = test_route_feasibility(all_orders, rider, shop_pem, dlv_pem) + if feasibility_check == 0: # feasible! + total_dist = get_total_distance(K, dist_mat, shop_pem, dlv_pem) + return Bundle(all_orders, rider, list(shop_pem), list(dlv_pem), bundle1.total_volume+bundle2.total_volume, total_dist) + + return None + +# 주어진 bundle의 배달원을 변경하는것이 가능한지 테스트 +# Note: 원래 bindle의 방문 순서가 최적이 아닐수도 있기 때문에 방문 순서 조합을 다시 확인함 +def try_bundle_rider_changing(all_orders, dist_mat, bundle, rider): + if bundle.rider.type != rider.type and bundle.total_volume <= rider.capa: + orders = bundle.shop_seq + for shop_pem in permutations(orders): + for dlv_pem in permutations(orders): + feasibility_check = test_route_feasibility(all_orders, rider, shop_pem, dlv_pem) + if feasibility_check == 0: # feasible! + # Note: in-place replacing! + bundle.shop_seq = list(shop_pem) + bundle.dlv_seq = list(dlv_pem) + bundle.rider = rider + bundle.total_dist = get_total_distance(len(all_orders), dist_mat, bundle.shop_seq, bundle.dlv_seq) + bundle.update_cost() + return True + + return False + +# 남아 있는 배달원 중에 *변동비*가 더 싼 배달원을 반환 +# 더 싼 배달원이 없으면 None 반환 +def get_cheaper_available_riders(all_riders, rider): + for r in all_riders: + if r.available_number > 0 and r.var_cost < rider.var_cost: + return r + + return None + +# 주어진 bundle list에서 임의로 두 개를 반환(중복없이) +def select_two_bundles(all_bundles): + bundle1, bundle2 = random.sample(all_bundles, 2) + return bundle1, bundle2 + +# 평균 비용(목적함수) 계산 +# = 총 비용 / 주문 수 +def get_avg_cost(all_orders, all_bundles): + return sum([bundle.cost for bundle in all_bundles]) / len(all_orders) + +# 주어진 bundle list에서 제출용 solution 포멧으로 반환 +def create_solution(prob_name, bundles): + sol = { + 'bundles' : [ + # rider type, shop_seq, dlv_seq + [bundle.rider.type, bundle.shop_seq, bundle.dlv_seq] + for bundle in bundles + ] + } + return sol + + + +# 주어진 solution의 feasibility를 테스트 +# Note: solution은 [배달원유형, pickup 순서, 배달 순서]의 list +# 반환하는 dict에는 solution이 feasible일 경우에는 평균 비용등의 정보가 추가적으로 포함됨 +# solution이 infeasible일 경우에는 그 이유가 'infeasibility' 항목(key)으로 반환 +def solution_check(K, all_orders, all_riders, dist_mat, solution): + + + total_cost = 0 + total_dist = 0 + + infeasibility = None + + if isinstance(solution, list): + + used_riders = { + 'CAR': 0, + 'WALK': 0, + 'BIKE': 0 + } + + all_deliveies = [] + + for bundle_info in solution: + if not isinstance(bundle_info, list) or len(bundle_info) != 3: + infeasibility = f'A bundle information must be a list of rider type, shop_seq, and dlv_seq! ===> {bundle_info}' + break + + rider_type = bundle_info[0] + shop_seq = bundle_info[1] + dlv_seq = bundle_info[2] + + # rider type check + if not rider_type in ['BIKE', 'WALK', 'CAR']: + infeasibility = f'Rider type must be either of BIKE, WALK, or CAR! ===> {rider_type}' + break + + # Get rider object + rider = None + for r in all_riders: + if r.type == rider_type: + rider = r + + # Increase used rider by 1 + used_riders[rider_type] += 1 + + # Pickup sequence check + if not isinstance(shop_seq, list): + infeasibility = f'The second bundle infomation must be a list of pickups! ===> {shop_seq}' + break + + for k in shop_seq: + if not isinstance(k, int) or k<0 or k>=K: + infeasibility = f'Pickup sequence has invalid order number: {k}' + break + + # Delivery sequence check + if not isinstance(dlv_seq, list): + infeasibility = f'The third bundle infomation must be a list of deliveries! ===> {dlv_seq}' + break + + for k in dlv_seq: + if not isinstance(k, int) or k<0 or k>=K: + infeasibility = f'Delivery sequence has invalid order number: {k}' + break + + # Pickup == delivery check + if set(shop_seq) != set(dlv_seq): + infeasibility = f'Sets of pickups and deliveries must be identical: {set(shop_seq)} != {set(dlv_seq)}' + break + + # Volume check + total_volume = get_total_volume(all_orders, shop_seq) + if total_volume > rider.capa: + infeasibility = f"Bundle's total volume exceeds the rider's capacity!: {total_volume} > {rider.capa}" + break + + # Deadline chaeck + pickup_times, dlv_times = get_pd_times(all_orders, rider.T, shop_seq, dlv_seq) + for k in dlv_seq: + all_deliveies.append(k) + if dlv_times[k] > all_orders[k].deadline: + infeasibility = f'Order {k} deadline is violated!: {dlv_times[k]} > {all_orders[k].deadline}' + break + + dist = get_total_distance(K, dist_mat, shop_seq, dlv_seq) + cost = rider.calculate_cost(dist) + + total_dist += dist + total_cost += cost + + if infeasibility is not None: + break + + + if infeasibility is None: + # Check used number of riders + for r in all_riders: + if r.available_number < used_riders[r.type]: + infeasibility = f'The number of used riders of type {r.type} exceeds the given available limit!' + break + + # Check deliveries + for k in range(K): + count = 0 + for k_sol in all_deliveies: + if k == k_sol: + count += 1 + + if count > 1: + infeasibility = f'Order {k} is assigned more than once! ===> {count} > 1' + break + elif count == 0: + infeasibility = f'Order {k} is NOT assigned!' + break + + else: + infeasibility = 'Solution must be a list of bundle information!' + + + if infeasibility is None: # All checks are passed! + checked_solution = { + 'total_cost': float(total_cost), + 'avg_cost': float(total_cost / K), + 'num_drivers': len(solution), + 'total_dist': int(total_dist), + 'feasible': True, + 'infeasibility': None, + 'bundles': solution + } + else: + print(infeasibility) + checked_solution = { + 'feasible': False, + 'infeasibility': infeasibility, + 'bundles': solution + } + + + return checked_solution + +# 주어진 solution의 경로를 visualize +def draw_route_solution(all_orders, solution=None): + + plt.subplots(figsize=(8, 8)) + node_size = 5 + + shop_x = [order.shop_lon for order in all_orders] + shop_y = [order.shop_lat for order in all_orders] + plt.scatter(shop_x, shop_y, c='red', s=node_size, label='SHOPS') + + dlv_x = [order.dlv_lon for order in all_orders] + dlv_y = [order.dlv_lat for order in all_orders] + plt.scatter(dlv_x, dlv_y, c='blue', s=node_size, label='DLVS') + + + if solution is not None: + + rider_idx = { + 'BIKE': 0, + 'CAR': 0, + 'WALK': 0 + } + + for bundle_info in solution['bundles']: + rider_type = bundle_info[0] + shop_seq = bundle_info[1] + dlv_seq = bundle_info[2] + + rider_idx[rider_type] += 1 + + route_color = 'gray' + if rider_type == 'BIKE': + route_color = 'green' + elif rider_type == 'WALK': + route_color = 'orange' + + route_x = [] + route_y = [] + for i in shop_seq: + route_x.append(all_orders[i].shop_lon) + route_y.append(all_orders[i].shop_lat) + + for i in dlv_seq: + route_x.append(all_orders[i].dlv_lon) + route_y.append(all_orders[i].dlv_lat) + + plt.plot(route_x, route_y, c=route_color, linewidth=0.5) + + plt.legend() + +# 주어진 soliution의 묶음 배송 방문 시간대를 visualize +def draw_bundle_solution(all_orders, all_riders, dist_mat, solution): + + plt.subplots(figsize=(6, len(solution['bundles']))) + + x_max = max([ord.deadline for ord in all_orders]) + + bundle_gap = 0.3 + y = 0.2 + + plt.yticks([]) + + for idx, bundle_info in enumerate(solution['bundles']): + rider_type = bundle_info[0] + shop_seq = bundle_info[1] + dlv_seq = bundle_info[2] + + rider = None + for r in all_riders: + if r.type == rider_type: + rider = r + + y_delta = 0.2 + + pickup_times, dlv_times = get_pd_times(all_orders, rider.T, shop_seq, dlv_seq) + + total_volume = 0 + for k in shop_seq: + total_volume += all_orders[k].volume # order volume + plt.hlines(y+y_delta/2, all_orders[k].ready_time, all_orders[k].deadline, colors='gray') + plt.vlines(all_orders[k].ready_time, y, y+y_delta, colors='gray') + plt.vlines(all_orders[k].deadline, y, y+y_delta, colors='gray') + + if total_volume > rider.capa: + plt.scatter(pickup_times[k], y+y_delta/2, c='red', zorder=100, marker='^', edgecolors='red', linewidth=0.5) + else: + plt.scatter(pickup_times[k], y+y_delta/2, c='green', zorder=100) + + if dlv_times[k] > all_orders[k].deadline: + plt.scatter(dlv_times[k], y+y_delta/2, c='red', zorder=100, marker='*', edgecolors='red', linewidth=0.5) + else: + plt.scatter(dlv_times[k], y+y_delta/2, c='orange', zorder=100) + + plt.text(all_orders[k].ready_time, y+y_delta/2, f'{all_orders[k].ready_time} ', ha='right', va='center', c='white', fontsize=6) + plt.text(all_orders[k].deadline, y+y_delta/2, f' {all_orders[k].deadline}', ha='left', va='center', c='white', fontsize=6) + + y += y_delta + + dist = get_total_distance(len(all_orders), dist_mat, shop_seq, dlv_seq) + cost = rider.calculate_cost(dist) + + plt.text(0, y+y_delta, f'{rider_type}: {shop_seq}-{dlv_seq}, tot_cost={cost}, tot_dist={dist}', ha='left', va='top', c='gray', fontsize=8) + y += bundle_gap + plt.hlines(y, 0, x_max, colors='gray', linestyles='dotted') + y += y_delta/2 + + + plt.ylim(0, y) + +