def greedy_lookahead(grid, N=4, break_n=500): """ Greedy lookahead will find the house with the shortest distance to a battery When a batteries current capacity goes between a certain range the algortim will call the look function This look function will look at all possible combinations of houses to fill this battery Look will return the combination of houses with the shortest distance N = the average amount of houses you wish to look ahead break_n = is the number of iterations you want the algorithm to try finding a solution """ # initiate counter that will determine when to break counter = 0 # get all outputs all_outputs = [house.max_output for house in grid.unconnected_houses] # repeat till solution is found while grid.unconnected_houses != []: # best_dist is the best score on distance best_dist = float('inf') # loop over houses that are not connected for house in grid.unconnected_houses: # finds the lowest distance to battery for each house # select the house with the lowest distance lowest_dist_house = float('inf') for battery in grid.batteries: dist = distance(house.location, battery.location) if dist < lowest_dist_house and battery.current_capacity > house.max_output and battery.current_capacity > np.mean( all_outputs) * N: bat_id = battery.id house_id = house.id lowest_dist_house = dist elif min(all_outputs) < battery.current_capacity < np.mean( all_outputs) * N: if 0 < grid.range_connected(battery)[1] < 8: houses = look(grid, battery) for h in houses: grid.connect(h.id, battery.id) if lowest_dist_house < best_dist: connect_bat_id = bat_id connect_house_id = house_id best_dist = lowest_dist_house # connect house with lowest distance to closest battery grid.connect(connect_house_id, connect_bat_id) # break if stuck in infinit loop after certain n counter += 1 if counter > break_n: break # fill remaining grid if needed if grid.unconnected_houses != []: grid = Algoritmes.greedy(grid) return grid
def look(grid, battery): """ Look will find the best combination of houses to fill the current capacity of input battery, with the shortest total distance """ # get min and max of houses able to connect to battery range_con = grid.range_connected(battery) # initiate lowest_combination = [0, 0] all_outputs = [house.max_output for house in grid.unconnected_houses] best_dist = float('inf') out_houses = [] # in range of able to connect houses generate all possible combinations of house outputs for i in range(range_con[1]): # generate combination combinations = itertools.combinations(all_outputs, i) # find lowest combination for combi in combinations: if 0 < battery.current_capacity - sum( combi) < battery.current_capacity - sum( lowest_combination) and battery.current_capacity - sum( combi) < 5: # find houses houses = [] for output in combi: for house in grid.unconnected_houses: if house.max_output == output: houses.append(house) # calculate total distance total_dist = 0 for h in houses: total_dist += distance(h.location, battery.location) # if current combination has better distance save combination if total_dist < best_dist: best_dist = total_dist lowest_combination = combi out_houses = houses return out_houses
def find_closest_house(self, houses): """ Finds the closest house to this battery in input list """ # initiate distance to infinity and have no houses as closest smallest_distance = float('inf') smallest_distance_house = None # loop over houses to find closest house # checks if this house can fit current capcity of the battery for house in houses: dist = distance(house.location, self.location) if self.current_capacity > house.max_output: if dist < smallest_distance: smallest_distance = dist smallest_distance_house = house if smallest_distance_house == None: # print(f"No shortest distance found, probably because battery capacity is full. current capcity: {self.current_capacity}") return return smallest_distance_house
def greedy_alt(grid): """ Alternative greedy algorithm that connects houses in order of output (high to low) """ def max_output_func(house): """ returns max output of a specified house. """ return house.max_output counter = 0 while grid.unconnected_houses != []: # sort houses from largest to smallest grid.unconnected_houses.sort(key=max_output_func, reverse=True) for house in grid.unconnected_houses: # for the unconnected_houses, intialize the distance to infinity # and make sure it doen't have a closet battery smallest_dist = float('inf') closest_battery_id = None # if there is capacity left, connect the house to closet battery for battery in grid.batteries: dist = distance(house.location, battery.location) if dist < smallest_dist and battery.current_capacity > house.max_output: closest_battery_id = battery.id smallest_dist = dist grid.connect(house.id, closest_battery_id) counter += 1 if counter > MAX_ITERATIONS: break # fill last part with the other greedy if needed if grid.unconnected_houses != []: grid = Algoritmes.greedy(grid) return grid
def hillclimber_greedy(grid): """ This hillclimber algoritm checks if a swap between two houses can be made, and if so, if the swap would shorten the length of the path, if this is the case, the swap is made. Requires the grid as input. """ # set swap to true to start the loop swap = True # loops until no swaps can be made while swap == True: # sets swap to false swap = False # loops through the batteries for b1 in grid.batteries: # loops through the houses in the batteries for h1 in b1.routes: # loops through the batteries for b2 in grid.batteries: # loops through the houses in the batteries for h2 in b2.routes: b1cap = h1.house.max_output + b1.current_capacity b2cap = h2.house.max_output + b2.current_capacity # checks if a swap between two houses can be made if h1.house.max_output < b2cap and h2.house.max_output < b1cap: # calculate is the swap improves the length of the connections len_new = distance(h1.house.location, b2.location) + distance(h2.house.location, b1.location) len_old = h1.length + h2.length # makes the swap if the length is improved if swap == False and len_new < len_old and h1.house.id != h2.house.id: swap = grid.swap(h1, h2) break return grid
def upper_bound(self): """ Calculates the upper_bound based on the manhattan distance for the longest path for each house. Returns the total grid cost of the upper_bound """ all_longest = [] # loop over houses for each house loop over batteries # find the longest distance to a battery and append to output list for house in self.houses: current_house_longest = float('-inf') for battery in self.batteries: dist = distance(house.location, battery.location) if dist > current_house_longest: current_house_longest = dist all_longest.append(current_house_longest) # calculate lower bound costs upper_bound = 0 for battery in self.batteries: upper_bound += battery.cost upper_bound += sum(all_longest) * 9 return upper_bound
def hillclimber_greedy_double_swap(grid): """ This hillclimber algoritm checks if a swap between pairs of two houses can be made, and if so, if the swap would shorten the length of the path, if this is the case, the swap is made. """ swap = True # loops until no swaps can be made while swap == True: # sets swap to false swap = False grid = Algoritmes.hillclimber_greedy(grid) # loops through the batteries for b1 in grid.batteries: for h1 in b1.routes: for h2 in b1.routes: for b2 in grid.batteries: for h3 in b2.routes: for h4 in b2.routes: h1h2 = h1.house.max_output + h2.house.max_output h3h4 = h3.house.max_output + h4.house.max_output cap1 = h1h2 + b1.current_capacity cap2 = h3h4 + b2.current_capacity if h1 != h2 and h3 != h4 and h1h2 < cap2 and h3h4 < cap1: len_old = h1.length + h2.length + h3.length + h4.length # Find battery ids bat1Index = None bat3Index = None for idx, battery in enumerate( grid.batteries): if battery.id == h1.battery_id: bat1Index = idx if battery.id == h3.battery_id: bat3Index = idx # get all distances d1 = distance( h1.house.location, grid.batteries[bat3Index].location) d2 = distance( h2.house.location, grid.batteries[bat3Index].location) d3 = distance( h3.house.location, grid.batteries[bat1Index].location) d4 = distance( h4.house.location, grid.batteries[bat1Index].location) len_new = d1 + d2 + d3 + d4 # makes the swap if the length is improved if swap == False and len_new < len_old: swap = grid.swap(h1, h2, h3, h4) break return grid
def re_arrange(grid): """ Re-arrange for simulated_annealing """ found = False house1 = False while found == False: # get random house_ids r1 = random.randint(1, 150) r2 = random.randint(1, 150) # find house 1 while house1 == False: for battery in grid.batteries: for route in battery.routes: if route.house.id == r1: # save house 1 and battery 1 h1 = route b1 = battery max1 = h1.house.max_output + b1.current_capacity house1 = True break # find house 2 for battery in grid.batteries: for route in battery.routes: if route.house.id == r2: # save house 1 and battery 1 h2 = route b2 = battery max2 = h2.house.max_output + b2.current_capacity # Find battery ids bat1Index = None bat2Index = None for idx, battery in enumerate(grid.batteries): if battery.id == h1.battery_id: bat1Index = idx if battery.id == h2.battery_id: bat2Index = idx # if swap is possible, swap if h1.house.max_output < max2 and h2.house.max_output < max1 and h1.battery_id != h2.battery_id: # calculate is the swap improves the length of the connections h1len = distance(h1.house.location, grid.batteries[bat2Index].location) h2len = distance(h2.house.location, grid.batteries[bat1Index].location) lengte_new = h1len + h2len lengte_old = h1.length + h2.length # stop while loop found = True break # in case we cannot find a house to swap with house 1, we need to reset the loop # without this you might get stuck in a loop house1 = False # return all the necessary information proposed = (lengte_new - lengte_old) * 9 h1 = h1 h2 = h2 return proposed, h1, h2