-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathchecker.py
312 lines (250 loc) · 13.5 KB
/
checker.py
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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
import problem as pb
import numpy as np
from itertools import combinations
# Checks if the solution is a solution that can be checked
# * right number of elements
# * values in [1,data.nbMachines]
def checkVector(data: pb.Data, solution: pb.Solution) -> bool:
# Checking that the solution vector is the right one
# if verbose:
# print("Checking the size of the vector")
if len(solution.assignment) != data.nbProcess:
print("The solution does not have the correct size")
print("Size = ", len(solution.assignment), " vs number of processes = ", data.nbProcess)
print("Test failed")
return False
# else:
# if verbose:
# print("Test passed")
# if verbose:
# print("Checking that the values in the solution are machine indices")
for p in range(data.nbProcess):
if solution.assignment[p] > data.nbMachines or solution.assignment[p] < 0:
print("The machine ID is wrong for process ", p, " : current value =", solution.assignment[p])
print("Test failed")
return False
# if verbose:
# print("Test passed")
return True
def checkCapacity(data: pb.Data, solution: pb.Solution, verbose: bool) -> bool:
if verbose:
print("Checking capacity constraint (without transient)")
# create a vector with 0 consumption for each machine * resource
resourceConsumption = np.zeros((data.nbMachines, data.nbResources), dtype=np.int64)
ok = True
for r in range(data.nbResources): # for each resource
# computing the resource consumption of the processes on each machine
for p in range(data.nbProcess): # for each process
# add its resource consumption for the machine to which it is assigned
resourceConsumption[solution.assignment[p], r] += data.processReq[p, r]
# checking hard resource consumption for each machine
for m in range(data.nbMachines):
if resourceConsumption[m, r] > data.hardResCapacities[m, r]:
print("Capacity violation for machine ", m, " and resource ", r)
print(resourceConsumption[m, r], " > ", data.hardResCapacities[m, r])
ok = False
if verbose:
if ok:
print("Test passed")
else:
print("Test failed")
if verbose:
print("Checking capacity constraint (with transient)")
for r in range(data.nbResources): # for each resource
if data.transientStatus[r] == 1: # if it is transient
# compute the resource consumption (including processes that were originally assigned to the machine )
for p in range(data.nbProcess):
if solution.assignment[p] != data.initialAssignment[p]: # if the process has moved
resourceConsumption[data.initialAssignment[p], r] += data.processReq[p,
r] # add its resource consumption
# checking hard resource consumption for each machine (including transient processes)
for m in range(data.nbMachines):
if resourceConsumption[m, r] > data.hardResCapacities[m, r]:
print("When transient resources considered, capacity violation for machine ", m, " and resource ",
r)
print(resourceConsumption[m, r], " > ", data.hardResCapacities[m, r])
ok = False
if verbose:
if ok:
print("Test passed")
else:
print("Test failed")
return ok
def checkConflict(data: pb.Data, solution: pb.Solution, verbose: bool) -> bool:
if verbose:
print("Checking disjunctions between processes of the same service")
ok = True
processesOfService = np.frompyfunc(list, 0, 1)(np.empty(data.nbServices, dtype=object))
for p in range(data.nbProcess):
processesOfService[data.servicesProcess[p]].append(p)
for s in range(data.nbServices):
for (i, j) in combinations(processesOfService[s], 2):
# if i and j are in the same service and on the same machine there is a problem
if solution.assignment[i] == solution.assignment[j]:
print("Disjunction constraint violation between processes ", i, " and ", j)
print("Both belong to service", s, " and are assigned to machine ",
solution.assignment[i])
ok = False
if verbose:
if ok:
print("Test passed")
else:
print("Test failed")
return ok
def checkSpread(data: pb.Data, solution: pb.Solution, verbose: bool) -> bool:
if verbose:
print("Checking spread constraints ")
ok = True
processesOfService = np.frompyfunc(list, 0, 1)(np.empty(data.nbServices, dtype=object))
for p in range(data.nbProcess):
processesOfService[data.servicesProcess[p]].append(p)
for s in range(data.nbServices): # for each service
if data.spreadMin[s] != 0: # if it has a spread min
locationsWithService = np.zeros(data.nbLocations,
dtype=np.int64) # table used to know which locations have service s
for p in processesOfService[s]: # for each process of service s
locationsWithService[
data.locations[solution.assignment[p]]] = 1 # note that its current location has its service
nbLocationsWithService = sum(locationsWithService) # number of locations with this service
if nbLocationsWithService < data.spreadMin[s]: # should be larger than the spread
print("Spread constraint violated for service ", s)
print("Present in ", nbLocationsWithService, " locations < ", data.spreadMin[s])
ok = False
if verbose:
if ok:
print("Test passed")
else:
print("Test failed")
return ok
def checkDependency(data: pb.Data, solution: pb.Solution, verbose: bool) -> bool:
if verbose:
print("Checking dependency constraints")
ok = True
# table indicating for each neighborhood if it currently has the service
servicesInNeighborhood = np.zeros((data.nbServices, data.nbNeighborhoods), dtype=np.int64)
for p in range(data.nbProcess):
servicesInNeighborhood[data.servicesProcess[p], data.neighborhoods[solution.assignment[p]]] = 1
for s in range(data.nbServices):
for n in range(data.nbNeighborhoods):
for d in data.dependencies[s]:
# there cannot be s and not t
if servicesInNeighborhood[s, n] == 1 and servicesInNeighborhood[d, n] == 0:
print("Dependency constraint violation")
print("Service ", s, " is in neighborhood ", n, " while service ", d, " is not")
ok = False
if verbose:
if ok:
print("Test passed")
else:
print("Test failed")
return ok
# Checks that the given solution is feasible
# Return True if the solution is feasible, False otherwise
def checkConstraints(data: pb.Data, solution: pb.Solution, verbose: bool) -> bool:
# Printing the arguments
if verbose:
print("-> Checking the feasibility of the solution ")
if not checkVector(data, solution):
print("ERROR: Cannot check the solution")
return False
ok = True
ok = checkCapacity(data, solution, verbose) and ok
ok = checkConflict(data, solution, verbose) and ok
ok = checkSpread(data, solution, verbose) and ok
ok = checkDependency(data, solution, verbose) and ok
if verbose:
if ok:
print("All tests passed: the solution is feasible ")
else:
print("ERROR: One or several tests failed: the solution is infeasible")
return ok
def computeLoadCost(data: pb.Data, solution: pb.Solution) -> int:
val = 0
# resource consumption for each machine and each resource
resourceConsumption = np.zeros((data.nbMachines, data.nbResources), dtype=np.int64)
for r in range(data.nbResources):
for p in range(data.nbProcess):
resourceConsumption[solution.assignment[p], r] += data.processReq[p, r]
# compute the load cost by summing the cost over machines and resources
for m in range(data.nbMachines):
for r in range(data.nbResources):
# the cost is accounted only if is larger than 0
if data.weightLoadCost[r] * max(0, resourceConsumption[m, r] - data.softResCapacities[m, r] > 0):
print('Machine ', m, ' Resource ', r, ' U('+str(m)+','+str(r)+') = ', resourceConsumption[m, r], ' SC('+str(m)+','+str(r)+') = ', data.softResCapacities[m, r],
' weight = ', data.weightLoadCost[r], ' loadCost = ', data.weightLoadCost[r] * max(0, resourceConsumption[m, r] - data.softResCapacities[m, r]))
val += data.weightLoadCost[r] * max(0, resourceConsumption[m, r] - data.softResCapacities[m, r])
return val
def computeBalanceCost(data: pb.Data, solution: pb.Solution) -> int:
totalBalanceCost = 0
# for each balance cost data
for b in data.balanceTriples:
# table that will contain the remaining capacity for resource r1 on each machine
slack_r1 = data.hardResCapacities[:, b.resource1].copy()
# table that will contain the remaining capacity for resource r2 on each machine
slack_r2 = data.hardResCapacities[:, b.resource2].copy()
# for each process, remove its resource requirement from the remaining capacity
for p in range(data.nbProcess):
slack_r1[solution.assignment[p]] -= data.processReq[p, b.resource1]
slack_r2[solution.assignment[p]] -= data.processReq[p, b.resource2]
localBalanceCost = 0 # balance cost for the current value of b
# sum over all machines of the balance costs
for m in range(data.nbMachines):
if max(0, b.target * slack_r1[m] - slack_r2[m]) > 0:
print('Machine ', m,' ', 'A('+str(m)+','+str(b.resource1)+') = ', slack_r1[m], ' A('+str(m)+','+str(b.resource2)+') = ', slack_r2[m],
'target = ', b.target , ' balanceCost = ', max(0, b.target * slack_r1[m] - slack_r2[m]))
localBalanceCost += max(0, b.target * slack_r1[m] - slack_r2[m])
totalBalanceCost += b.weight * localBalanceCost
return totalBalanceCost
def computeProcessMoveCost(data: pb.Data, solution: pb.Solution) -> int:
val = 0
# count the number of processes that are not in the same machine after optimizing
for p in range(data.nbProcess):
if data.initialAssignment[p] != solution.assignment[p]:
val += data.processMoveCost[p]
return val
def computeMachineMoveCost(data: pb.Data, solution: pb.Solution) -> int:
val = 0
for p in range(data.nbProcess):
val += data.machineMoveCosts[data.initialAssignment[p], solution.assignment[p]]
return val
def computeServiceMoveCost(data: pb.Data, solution: pb.Solution) -> int:
nbMovesInService = np.zeros(data.nbServices, dtype=np.int64) # number of processes that moved inside each service
for p in range(data.nbProcess):
if data.initialAssignment[p] != solution.assignment[p]: # if it moved
nbMovesInService[data.servicesProcess[p]] += 1 # increase the corresponding service counter
return np.max(nbMovesInService) # return the maximum among all services
def getCost(data: pb.Data, solution: pb.Solution, verbose: bool) -> float:
loadCost = computeLoadCost(data, solution)
balanceCost = computeBalanceCost(data, solution)
processMoveCost = computeProcessMoveCost(data, solution)
machineMoveCost = computeMachineMoveCost(data, solution)
serviceMoveCost = computeServiceMoveCost(data, solution)
if verbose:
print("Load cost (computed by the checker) = ", loadCost)
print("Balance cost (computed by the checker) = ", balanceCost)
print("Process move cost (computed by the checker) = ", data.processMoveWeight * processMoveCost)
print("Machine move cost (computed by the checker) = ", data.machineMoveWeight * machineMoveCost)
print("Service move cost (computed by the checker) = ", data.serviceMoveWeight * serviceMoveCost)
totalCost = loadCost + balanceCost + data.processMoveWeight * processMoveCost + \
data.serviceMoveWeight * serviceMoveCost + data.machineMoveWeight * machineMoveCost
return totalCost
# Checks that the cost stored inside the given solution is correctly computed
# Return True if the cost of the solution is correctly computed, False otherwise
def checkCost(data: pb.Data, solution: pb.Solution, verbose: bool) -> bool:
# Printing the arguments
if verbose:
print("-> Checking the internal cost of solution ")
if not checkVector(data, solution):
print("ERROR: Cannot check the solution")
return False
totalCost = getCost(data, solution, verbose)
print("Objective function (computed by the checker) = ", totalCost)
print("Objective function (recorded in the solution) = ", solution.cost)
if totalCost != solution.cost:
print("ERROR: The cost recorded in the solution is not correct (set verbose to True for the detailed values)")
# Checks that the given solution : feasible ? cost correctly computed ?
# Return True if the solution is feasible and its cost is correctly computed, False otherwise
def check(data: pb.Data, solution: pb.Solution, verbose: bool) -> bool:
feasible = checkConstraints(data, solution, verbose)
costCorrectlyComputed = checkCost(data, solution, verbose)
return feasible and costCorrectlyComputed