Implement greedy solution. Reaches 35/80, 56 needed to pass.
This commit is contained in:
204
facility/facility.py
Executable file
204
facility/facility.py
Executable file
@@ -0,0 +1,204 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import math
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
Point = namedtuple("Point", ['x', 'y'])
|
||||||
|
Facility = namedtuple("Facility", ['index', 'setup_cost', 'capacity', 'location'])
|
||||||
|
Customer = namedtuple("Customer", ['index', 'demand', 'location'])
|
||||||
|
|
||||||
|
|
||||||
|
class Solution(object):
|
||||||
|
|
||||||
|
def __init__(self, facilities, customers):
|
||||||
|
self.facilities = facilities
|
||||||
|
self.customers = customers
|
||||||
|
|
||||||
|
self.cost = 0
|
||||||
|
self.facility_connected_customers = [set() for _ in facilities]
|
||||||
|
self.facility_remaining_capacity = [f.capacity for f in facilities]
|
||||||
|
self.customer_to_facility = [None for _ in customers]
|
||||||
|
|
||||||
|
def connect(self, customer_index, facility_index):
|
||||||
|
customer = self.customers[customer_index]
|
||||||
|
facility = self.facilities[facility_index]
|
||||||
|
|
||||||
|
# If customers is already connected handle disconnect properly.
|
||||||
|
if (connected_facitlity_index := self.customer_to_facility[customer_index]):
|
||||||
|
self.disconnect(customer_index, connected_facitlity_index)
|
||||||
|
|
||||||
|
# If facility is currently not used we have to set it up.
|
||||||
|
if not self.facility_connected_customers[facility_index]:
|
||||||
|
self.cost += facility.setup_cost
|
||||||
|
|
||||||
|
self.facility_connected_customers[facility_index].add(customer_index)
|
||||||
|
if self.facility_remaining_capacity[facility_index] < customer.demand:
|
||||||
|
raise Exception(f"Cannot connect {customer} to {facility}.")
|
||||||
|
self.facility_remaining_capacity[facility_index] -= customer.demand
|
||||||
|
|
||||||
|
self.customer_to_facility[customer_index] = facility_index
|
||||||
|
self.cost += length(facility.location, customer.location)
|
||||||
|
|
||||||
|
def disconnect(self, customer_index, facility_index):
|
||||||
|
customer = self.customers[customer_index]
|
||||||
|
facility = self.facilities[facility_index]
|
||||||
|
self.cost -= length(facility.location, customer.location)
|
||||||
|
|
||||||
|
self.facility_connected_customers[facility_index].remove(customer_index)
|
||||||
|
self.facility_remaining_capacity[facility_index] += customer.demand
|
||||||
|
|
||||||
|
self.customer_to_facility[customer_index] = None
|
||||||
|
|
||||||
|
if not self.facility_connected_customers[facility_index]:
|
||||||
|
self.cost -= self.facilities[facility_index].setup_cost
|
||||||
|
|
||||||
|
def get_feasible_facilities(self, customer_index):
|
||||||
|
customer = self.customers[customer_index]
|
||||||
|
facility_indices = [f.index
|
||||||
|
for f in self.facilities
|
||||||
|
if self.facility_remaining_capacity[f.index] >= customer.demand]
|
||||||
|
if not facility_indices:
|
||||||
|
raise Exception("No feasible facilities.")
|
||||||
|
|
||||||
|
def key(facility_index):
|
||||||
|
cost = 0
|
||||||
|
facility = self.facilities[facility_index]
|
||||||
|
# If there are customers yet we have to open it.
|
||||||
|
if not self.facility_connected_customers[facility_index]:
|
||||||
|
cost += facility.setup_cost
|
||||||
|
cost += length(customer.location, facility.location)
|
||||||
|
return cost
|
||||||
|
facility_indices.sort(key=key)
|
||||||
|
return facility_indices
|
||||||
|
|
||||||
|
def is_valid(self):
|
||||||
|
for customer in self.customers:
|
||||||
|
if self.customer_to_facility[customer.index] is None:
|
||||||
|
raise Exception(f"{customer} not connected.")
|
||||||
|
for facility in self.facilities:
|
||||||
|
if self.facility_remaining_capacity[facility.index] < 0:
|
||||||
|
raise Exception(f"{facility} exceeds capacity.")
|
||||||
|
|
||||||
|
cost = sum([f.setup_cost
|
||||||
|
for f in self.facilities
|
||||||
|
if self.facility_connected_customers[f.index]])
|
||||||
|
|
||||||
|
for customer in self.customers:
|
||||||
|
facility = self.facilities[self.customer_to_facility[customer.index]]
|
||||||
|
|
||||||
|
cost += length(facility.location, customer.location)
|
||||||
|
|
||||||
|
if abs(cost - self.cost) > 0.00001:
|
||||||
|
raise Exception(f"Running cost {self.cost} unequal to actual cost {cost}.")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def plot_map(self):
|
||||||
|
try:
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import matplotlib.lines as lines
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
return
|
||||||
|
|
||||||
|
figure = plt.figure()
|
||||||
|
|
||||||
|
for f in self.facilities:
|
||||||
|
x, y = f.location
|
||||||
|
color = 'ro' if self.facility_connected_customers[f.index] else 'go'
|
||||||
|
plt.plot(x, y, color)
|
||||||
|
rem_cap = self.facility_remaining_capacity[f.index]
|
||||||
|
plt.text(x, y, f" F({f.index}, {f.setup_cost}, {rem_cap}/{f.capacity})")
|
||||||
|
|
||||||
|
for c in self.customers:
|
||||||
|
x, y = c.location
|
||||||
|
plt.plot(x, y, 'bx')
|
||||||
|
plt.text(x, y, f" C({c.index}, {c.demand})")
|
||||||
|
if (f_index := self.customer_to_facility[c.index]) is not None:
|
||||||
|
f = self.facilities[f_index]
|
||||||
|
x_f, y_f = f.location
|
||||||
|
plt.plot([x, x_f], [y, y_f], 'b-')
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
def get_facilities_by_customers(self):
|
||||||
|
facility_indices = [f.index for f in self.facilities
|
||||||
|
if self.facility_connected_customers[f.index]]
|
||||||
|
def key(facility_index):
|
||||||
|
return len(self.facility_connected_customers[facility_index])
|
||||||
|
facility_indices.sort(key=key)
|
||||||
|
return facility_indices
|
||||||
|
|
||||||
|
def to_output_data(self):
|
||||||
|
# calculate the cost of the solution
|
||||||
|
self.is_valid()
|
||||||
|
obj = self.cost
|
||||||
|
# prepare the solution in the specified output format
|
||||||
|
output_data = '%.2f' % obj + ' ' + str(0) + '\n'
|
||||||
|
output_data += ' '.join(map(str, self.customer_to_facility))
|
||||||
|
return output_data
|
||||||
|
|
||||||
|
|
||||||
|
def length(point1, point2):
|
||||||
|
return math.sqrt((point1.x - point2.x)**2 + (point1.y - point2.y)**2)
|
||||||
|
|
||||||
|
|
||||||
|
def build_trivial_solution(solution):
|
||||||
|
# build a trivial solution
|
||||||
|
# pack the facilities one by one until all the customers are served
|
||||||
|
facility_index = 0
|
||||||
|
for customer in solution.customers:
|
||||||
|
if solution.facility_remaining_capacity[facility_index] >= customer.demand:
|
||||||
|
solution.connect(customer.index, facility_index)
|
||||||
|
else:
|
||||||
|
facility_index += 1
|
||||||
|
solution.connect(customer.index, facility_index)
|
||||||
|
return solution
|
||||||
|
|
||||||
|
|
||||||
|
def build_greedy_solution(solution):
|
||||||
|
for customer in solution.customers:
|
||||||
|
facility_index = solution.get_feasible_facilities(customer.index)[0]
|
||||||
|
solution.connect(customer.index, facility_index)
|
||||||
|
return solution
|
||||||
|
|
||||||
|
|
||||||
|
def solve_it(input_data):
|
||||||
|
facilities, customers = parse(input_data)
|
||||||
|
solution = Solution(facilities, customers)
|
||||||
|
build_greedy_solution(solution)
|
||||||
|
solution.plot_map()
|
||||||
|
output_data = solution.to_output_data()
|
||||||
|
return output_data
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
file_location = "data/fl_3_1"
|
||||||
|
with open(file_location, 'r') as input_data_file:
|
||||||
|
input_data = input_data_file.read()
|
||||||
|
print(solve_it(input_data))
|
||||||
|
|
||||||
|
|
||||||
|
def parse(input_data):
|
||||||
|
# parse the input
|
||||||
|
lines = input_data.split('\n')
|
||||||
|
|
||||||
|
parts = lines[0].split()
|
||||||
|
facility_count = int(parts[0])
|
||||||
|
customer_count = int(parts[1])
|
||||||
|
|
||||||
|
facilities = []
|
||||||
|
for i in range(1, facility_count+1):
|
||||||
|
parts = lines[i].split()
|
||||||
|
facilities.append(Facility(i-1, float(parts[0]), int(parts[1]),
|
||||||
|
Point(float(parts[2]), float(parts[3])) ))
|
||||||
|
|
||||||
|
customers = []
|
||||||
|
for i in range(facility_count+1, facility_count+1+customer_count):
|
||||||
|
parts = lines[i].split()
|
||||||
|
customers.append(Customer(i-1-facility_count, int(parts[0]),
|
||||||
|
Point(float(parts[1]), float(parts[2]))))
|
||||||
|
|
||||||
|
return facilities, customers
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
from facility import solve_it
|
||||||
import math
|
import math
|
||||||
|
|
||||||
Point = namedtuple("Point", ['x', 'y'])
|
Point = namedtuple("Point", ['x', 'y'])
|
||||||
@@ -11,7 +12,7 @@ Customer = namedtuple("Customer", ['index', 'demand', 'location'])
|
|||||||
def length(point1, point2):
|
def length(point1, point2):
|
||||||
return math.sqrt((point1.x - point2.x)**2 + (point1.y - point2.y)**2)
|
return math.sqrt((point1.x - point2.x)**2 + (point1.y - point2.y)**2)
|
||||||
|
|
||||||
def solve_it(input_data):
|
def solve_it_example(input_data):
|
||||||
# Modify this code to run your optimization algorithm
|
# Modify this code to run your optimization algorithm
|
||||||
|
|
||||||
# parse the input
|
# parse the input
|
||||||
@@ -63,8 +64,6 @@ def solve_it(input_data):
|
|||||||
return output_data
|
return output_data
|
||||||
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import sys
|
import sys
|
||||||
if len(sys.argv) > 1:
|
if len(sys.argv) > 1:
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ def output(input_file, solver_file):
|
|||||||
|
|
||||||
solution = ''
|
solution = ''
|
||||||
|
|
||||||
start = time.clock()
|
start = time.process_time()
|
||||||
try:
|
try:
|
||||||
solution = pkg.solve_it(load_input_data(input_file))
|
solution = pkg.solve_it(load_input_data(input_file))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -224,7 +224,7 @@ def output(input_file, solver_file):
|
|||||||
print(str(e))
|
print(str(e))
|
||||||
print('')
|
print('')
|
||||||
return 'Local Exception =('
|
return 'Local Exception =('
|
||||||
end = time.clock()
|
end = time.process_time()
|
||||||
|
|
||||||
if not (isinstance(solution, str) or isinstance(solution, unicode)):
|
if not (isinstance(solution, str) or isinstance(solution, unicode)):
|
||||||
print('Warning: the solver did not return a string. The given object will be converted with the str() method.')
|
print('Warning: the solver did not return a string. The given object will be converted with the str() method.')
|
||||||
|
|||||||
Reference in New Issue
Block a user