-
Notifications
You must be signed in to change notification settings - Fork 0
중점을 이용한 bundle merge #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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): | ||
| 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): | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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): | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
| 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)]) | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 길이가 1인 번들이 있는 경우에 우선권을 주기 위한 코드. |
||
|
|
||
| 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 | ||
|
|
||
There was a problem hiding this comment.
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해주는 함수
(픽업 위도, 픽업 경도), (배송 위도, 배송 경도) 형태