diff --git a/facility/facility.py b/facility/facility.py new file mode 100755 index 0000000..d076c22 --- /dev/null +++ b/facility/facility.py @@ -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() + diff --git a/facility/solver.py b/facility/solver.py index 930b5d8..ca0a4bc 100755 --- a/facility/solver.py +++ b/facility/solver.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- from collections import namedtuple +from facility import solve_it import math Point = namedtuple("Point", ['x', 'y']) @@ -11,7 +12,7 @@ Customer = namedtuple("Customer", ['index', 'demand', 'location']) def length(point1, point2): 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 # parse the input @@ -20,7 +21,7 @@ def solve_it(input_data): 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() @@ -63,8 +64,6 @@ def solve_it(input_data): return output_data -import sys - if __name__ == '__main__': import sys if len(sys.argv) > 1: diff --git a/facility/submit.py b/facility/submit.py index c0bf744..3853d91 100755 --- a/facility/submit.py +++ b/facility/submit.py @@ -214,7 +214,7 @@ def output(input_file, solver_file): solution = '' - start = time.clock() + start = time.process_time() try: solution = pkg.solve_it(load_input_data(input_file)) except Exception as e: @@ -224,7 +224,7 @@ def output(input_file, solver_file): print(str(e)) print('') return 'Local Exception =(' - end = time.clock() + end = time.process_time() 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.')