def test_in_path(self): data = [(4, None, True), (2, None, True), (7, None, True), (3, None, False), (7, 6, True), (4, 1, True), (8, 3, True), (4, 0, False), (7, 5, False), (10, 3, False)] path = Path(path=[4, 1, 8, 2, 9, 7]) for city, limit, expected in data: with self.subTest(city=city, limit=limit): result = path.in_path(city, limit) self.assertEqual(result, expected)
def _crossover_nwox(self, parent1, parent2): """Performs non wrapping order crossover to create a child path from two given parents paths. :param Path parent1: First parent path. :param Path parent2: Second parent path. :return: Child path. :rtype: Path """ # Initial child path child = Path(self.tsp.dimension + 1) # Copy random subpath from parent 1 to child start, end = self._rand_subpath() child[start:end + 1] = parent1[start:end + 1] # Fill in child's empty slots with cities from parent 2 in order parent_pos = child_pos = 0 while parent_pos < self.tsp.dimension + 1: # Skip already filled subpath if start <= child_pos <= end: child_pos = end + 1 continue # Get city from parent path city = parent2[parent_pos] if child.in_path(city): # If this city is already in child path then go to next one parent_pos += 1 continue else: # Otherwise add it to child path and go to next child[child_pos] = city child_pos += 1 parent_pos += 1 # Add return to 0 if last stop is empty child[-1] = child[-1] if child[-1] != -1 else 0 child.distance = self.tsp.path_dist(child) return child
def solve(self, tsp, steps=True): # Make sure given argument is of correct type if not isinstance(tsp, TSP): raise TypeError('solve() argument has to be of type \'TSP\'') self.tsp = tsp # Path will always start and end in 0 path = Path(self.tsp.dimension + 1) path[0] = path[-1] = 0 # Start timer self._start_timer() # For each stop except the first and last one for i in range(1, len(path) - 1): prev = path[i - 1] min_dist = inf # Check all connections to different cities for j in range(self.tsp.dimension): # Skip cities that already are in path if path.in_path(j, i): continue # Keep the new distance if it's lower than current minimum new_dist = self.tsp.dist(prev, j) if new_dist < min_dist: min_dist = new_dist path[i] = j if steps: progress = i / (len(path) - 1) yield SolverState(self._time(), progress, deepcopy(path), None) path.distance = self.tsp.path_dist(path) yield SolverState(self._time(), 1, None, deepcopy(path), True)
def solve(self, tsp, steps=True): # Make sure given argument is of correct type if not isinstance(tsp, TSP): raise TypeError('solve() argument has to be of type \'TSP\'') self.tsp = tsp # Total and current number of steps for calculating progress if steps: total = factorial(self.tsp.dimension - 1) * 2 current = 0 # Working path path = Path(self.tsp.dimension + 1) path[-1] = 0 # Minimal path and distance min_path = Path(self.tsp.dimension + 1) min_path.distance = inf # Nodes list (used as a stack) stack = [] # Add starting city (0) to the stack stack.append((0, 0, 0)) # Start the timer self._start_timer() while len(stack) > 0: # Increment step counter if steps: current += 1 # Get node from the top of the stack cur_node = stack.pop() city, dist, level = cur_node # Update current path with this node path[level] = city # This is the level of all children of this node next_level = level + 1 # If it's the last level of the tree if level == self.tsp.dimension - 1: path.distance = dist + self.tsp.dist(city, 0) # Yield the current state if steps: yield SolverState(self._time(), current / total, deepcopy(path), deepcopy(min_path)) # Distance of full path with return to 0 # Keep it if it's better than the current minimum if path.distance < min_path.distance: min_path = deepcopy(path) else: continue # Iterate through all cities for i in range(self.tsp.dimension): # Skip current city itself, its predecessors and starting city if i == city or path.in_path(i, next_level) or i == 0: continue # Skip this node if its distance is greater than min path next_dist = dist + self.tsp.dist(city, i) if next_dist >= min_path.distance: continue # If it's valid node push it onto stack stack.append((i, next_dist, next_level)) yield SolverState(self._time(), 1, None, deepcopy(min_path), True)