-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlocal_search.py
More file actions
227 lines (187 loc) · 9.23 KB
/
local_search.py
File metadata and controls
227 lines (187 loc) · 9.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
from group import Group
from group_analysis import (
find_feasible_interval,
find_non_intersecting_pairs,
find_optimal_group_time,
compute_group_economic_profit,
compute_grouping_structure_cost
)
def swap_operator(groups, planning_horizon, get_production_line_by_id, tabu_list):
"""
Perform a swap operation between two groups.
Args:
groups: List of groups
planning_horizon: Planning horizon in days
get_production_line_by_id: Function to get production line by ID
Returns:
Tuple containing the best groups, components, and cost reduction
"""
best_cost_reduction = None
best_group1 = None
best_group2 = None
best_component1 = None
best_component2 = None
initial_cost = compute_grouping_structure_cost(groups, get_production_line_by_id, planning_horizon)
# Iterate through all pairs of groups
for i, group_1 in enumerate(groups):
for j, group_2 in enumerate(groups):
# Skip if it's the same group
if i == j:
continue
# Try swapping each component from group_1 with each component from group_2
for component_1 in group_1.components:
for component_2 in group_2.components:
# Swap components
if ("swap", component_1.id, component_2.id) in tabu_list:
continue
group_1.remove_component(component_1)
group_2.remove_component(component_2)
group_1.add_component(component_2)
group_2.add_component(component_1)
# Calculate the new cost
new_cost = compute_grouping_structure_cost(groups, get_production_line_by_id, planning_horizon)
# Calculate the cost reduction
cost_reduction = initial_cost - new_cost
# Check if this is the best swap so far
if best_cost_reduction is None or cost_reduction > best_cost_reduction:
best_cost_reduction = cost_reduction
best_group1 = group_1
best_group2 = group_2
best_component1 = component_1
best_component2 = component_2
# Swap back to original state
group_1.remove_component(component_2)
group_2.remove_component(component_1)
group_1.add_component(component_1)
group_2.add_component(component_2)
return best_group1, best_group2, best_component1, best_component2, best_cost_reduction
def apply_swap(groups, best_group1, best_group2, best_component1, best_component2):
"""
Apply the swap operation to the groups.
"""
# Perform the swap
best_group1.remove_component(best_component1)
best_group2.remove_component(best_component2)
best_group1.add_component(best_component2)
best_group2.add_component(best_component1)
def relocate_operator(groups, planning_horizon, get_production_line_by_id, tabu_list):
"""
Perform a relocation operation between two groups.
Args:
groups: List of groups
planning_horizon: Planning horizon in days
get_production_line_by_id: Function to get production line by ID
Returns:
Tuple containing the best groups, component, and cost reduction
"""
# Identify the component whose relocation leads to the best reduction in the grouping structure cost
# relocate a component from one group to another
best_cost_reduction = None
best_group1 = None
best_group2 = None
best_component = None
initial_cost = compute_grouping_structure_cost(groups, get_production_line_by_id, planning_horizon)
for i, group_1 in enumerate(groups):
for j, group_2 in enumerate(groups):
# Skip if it's the same group
if i >= j:
continue
# Use a copy of the components list to avoid modification issues during iteration
for component in group_1.components:
# Relocate component
if ("relocate", component.id, group_1.id, group_2.id) in tabu_list:
continue
group_1.remove_component(component)
group_2.add_component(component)
# Calculate the new cost
new_cost = compute_grouping_structure_cost(groups, get_production_line_by_id, planning_horizon)
# Calculate the cost reduction
cost_reduction = initial_cost - new_cost
# Check if this is the best relocation so far
if best_cost_reduction is None or cost_reduction > best_cost_reduction:
best_cost_reduction = cost_reduction
best_group1 = group_1
best_group2 = group_2
best_component = component
# Relocate back to original state
group_2.remove_component(component)
group_1.add_component(component)
return best_group1, best_group2, best_component, best_cost_reduction
def apply_relocate(groups, best_group1, best_group2, best_component):
"""
Apply the relocation operation to the groups.
"""
# Perform the relocation
best_group1.remove_component(best_component)
best_group2.add_component(best_component)
def clone_groups(groups):
"""
Clone groups without deep copying the components.
Keep the same component references (no deepcopy).
"""
from group import Group
cloned_groups = []
for group in groups:
new_group = Group(group.id)
for component in group.components:
new_group.add_component(component) # use the same reference
cloned_groups.append(new_group)
return cloned_groups
from collections import deque
def local_search_scheme(groups, get_production_line_by_id, planning_horizon, max_iterations=20, tabu_tenure=5):
"""
Perform local search to optimize the grouping structure cost.
Args:
groups: List of groups to optimize
get_production_line_by_id: Function to get production line by ID
planning_horizon: Planning horizon in days
max_iterations: Maximum number of iterations
Returns:
Best solution found during the search
"""
current_solution = groups
current_cost = compute_grouping_structure_cost(current_solution, get_production_line_by_id, planning_horizon)
# Track the global best solution
global_best_solution = clone_groups(current_solution)
global_best_cost = current_cost
tabu_list = deque(maxlen=tabu_tenure) # Deque is a double-ended queue that allows appending and popping from both ends
print(f"Initial cost: {current_cost:.2f}")
for iteration in range(max_iterations):
print(f"Iteration {iteration + 1}/{max_iterations}")
# Apply swap operator
best_swap = swap_operator(current_solution, planning_horizon, get_production_line_by_id, tabu_list)
# --- Relocate Operator (now respects Tabu list) ---
best_relocate = relocate_operator(current_solution, planning_horizon, get_production_line_by_id, tabu_list)
# --- Select best move ---
best_move = None
if best_swap:
_, _, _, _, swap_reduction = best_swap
# Aspiration: Override tabu if this swap beats global best
if ("swap", best_swap[2].id, best_swap[3].id) not in tabu_list or (current_cost - swap_reduction) < global_best_cost:
best_move = ("swap", best_swap)
if best_relocate:
_, _, _, relocate_reduction = best_relocate
# Aspiration: Override tabu if this relocate beats global best
if ("relocate", best_relocate[2].id, best_relocate[0].id, best_relocate[1].id) not in tabu_list or (current_cost - relocate_reduction) < global_best_cost:
if best_move is None or relocate_reduction > best_swap[4]:
best_move = ("relocate", best_relocate)
# --- Apply the best move ---
if best_move:
move_type, move_data = best_move
if move_type == "swap":
g1, g2, c1, c2, cost_reduction = move_data
print(f"Applying swap: {c1.id} <-> {c2.id}, Cost reduction: {cost_reduction:.2f}")
apply_swap(current_solution, g1, g2, c1, c2)
tabu_list.append(("swap", c1.id, c2.id)) # Add to tabu list
current_cost -= cost_reduction
else:
g1, g2, c, cost_reduction = move_data
print(f"Applying relocate: {c.id} from {g1.id} to {g2.id}, Cost reduction: {cost_reduction:.2f}")
apply_relocate(current_solution, g1, g2, c)
tabu_list.append(("relocate", c.id, g1.id, g2.id)) # Add to tabu list
current_cost -= cost_reduction
# Update global best
if current_cost < global_best_cost:
global_best_solution = clone_groups(current_solution)
global_best_cost = current_cost
return global_best_solution