Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added Mid_point_240805/Mid_point_240805.zip
Binary file not shown.
145 changes: 145 additions & 0 deletions Mid_point_240805/custom_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
from util import *

from itertools import permutations

import math

def custom_try_merging_bundles(K, dist_mat, all_orders, bundle1, bundle2, all_riders):
merged_orders = bundle1.shop_seq + bundle2.shop_seq
total_volume = get_total_volume(all_orders, merged_orders)
best_bundle = None
min_total_cost = float('inf')
for rider in all_riders:
if rider.available_number > 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):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avg_loc 함수: all_bundles를 넣으면, 각 번들의 픽업 지점과 배송 지점의 중점 좌표를 계산해서 return해주는 함수
(픽업 위도, 픽업 경도), (배송 위도, 배송 경도) 형태

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):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

haversine_distance 함수: 두 지점의 위도와 경도를 입력하면 두 지점 사이의 거리를 계산해주는 함수

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):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dist_mat_by_loc 함수: 모든 번들들의 중점 좌표를 입력하면, 이를 통해 번들 간의 거리 matrix를 return해주는 함수. 이때 거리는 픽업, 배송을 모두 고려하여(회의 때 설명한 0.25 가중치 사용하는 그 공식) 하나의 값으로 계산함.


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
233 changes: 233 additions & 0 deletions Mid_point_240805/myalgorithm.py
Original file line number Diff line number Diff line change
@@ -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)])
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

두 번들 간의 거리를 계산하여 heapq에 넣어 거리가 작은 순서로 뽑아낼 수 있게 함


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
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

길이가 1인 번들이 있는 경우에 우선권을 주기 위한 코드.
그렇다고 모든 길이가 1인 번들을 처리하려고 하면, 마지막 남은 몇개의 번들이 억지로 묶일 가능성이 있어서, 길이가 1인 번들이 3개 일 때 까지만 우선권을 부여.


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

Loading