Skip to content

Instantly share code, notes, and snippets.

@stong

stong/tsp.py Secret

Created Sep 10, 2021
Embed
What would you like to do?

Revisions

  1. stong revised this gist Sep 10, 2021. 1 changed file with 37 additions and 6 deletions.
    43 tsp.py
    @@ -1,6 +1,12 @@
    # Python 3 # Python 3
    # Genetic algorithm approximation for Traveling Salesman Problem # Genetic algorithm approximation for Traveling Salesman Problem


    import random
    import math
    POPULATION_SIZE = 50
    GENERATIONS = 10000
    PARENTS_COUNT = 50

    def tsp_solve(network): def tsp_solve(network):
    """ """
    Solves the TSP using a genetic algorithm. Solves the TSP using a genetic algorithm.
    @@ -23,6 +29,8 @@ def tsp_solve(network):


    # Evolve the population # Evolve the population
    for i in range(GENERATIONS): for i in range(GENERATIONS):
    print(i, population[0][1])

    # Select parents for mating # Select parents for mating
    parents = [] parents = []


    @@ -44,12 +52,28 @@ def tsp_solve(network):
    children = sorted(children, key=lambda x: x[1]) children = sorted(children, key=lambda x: x[1])


    # Replace least fit population with new children # Replace least fit population with new children
    for j in range(POPULATION_SIZE): population = merge(population, children)[:POPULATION_SIZE]
    population[j] = children[j]


    # Return most fit path # Return most fit path
    return population[0] return population[0]


    # Merge sorted lists, based on the value of their second element. Without modifying either list
    def merge(a, b): # (This code also generated by codex from the prompt above)
    result = []
    i = 0
    j = 0
    while i < len(a) and j < len(b):
    if a[i][1] <= b[j][1]:
    result.append(a[i])
    i += 1
    else:
    result.append(b[j])
    j += 1
    if i < len(a):
    result.extend(a[i:])
    if j < len(b):
    result.extend(b[j:])
    return result


    def random_path(network): def random_path(network):
    """ """
    @@ -101,6 +125,11 @@ def fitness(path, network):
    # Add distance from last node to first node # Add distance from last node to first node
    length += distance(path[len(path) - 1], path[0]) length += distance(path[len(path) - 1], path[0])


    # penalize not having all nodes
    for pt in network:
    if not pt in path:
    length *= 2

    # Return length # Return length
    return length return length


    @@ -129,8 +158,8 @@ def crossover(parents):
    child2 = parent2[0][:cross_point] + parent1[0][cross_point:] child2 = parent2[0][:cross_point] + parent1[0][cross_point:]


    # Add children to list of children # Add children to list of children
    children.append([child1, 0]) children.append(child1)
    children.append([child2, 0]) children.append(child2)


    # Return list of children # Return list of children
    return children return children
    @@ -155,7 +184,9 @@ def mutation(path):
    point2 = random.randint(0, len(path) - 1) point2 = random.randint(0, len(path) - 1)


    # Swap nodes at the two points # Swap nodes at the two points
    new_path = path[:point1] + path[point2:point2 + 1] + path[point1 + 1:point2] + path[point1:point1 + 1] + path[point2 + 1:] #new_path = path[:point1] + path[point2:point2 + 1] + path[point1 + 1:point2] + path[point1:point1 + 1] + path[point2 + 1:]
    new_path = path[:]
    new_path[point1], new_path[point2] = new_path[point2], new_path[point1]


    # Return mutated path # Return mutated path
    return [new_path, 0] return [new_path, 0]
    @@ -207,4 +238,4 @@ def main():




    if __name__ == "__main__": if __name__ == "__main__":
    main() main()
  2. stong created this gist Sep 10, 2021.
    210 tsp.py
    @@ -0,0 +1,210 @@
    # Python 3
    # Genetic algorithm approximation for Traveling Salesman Problem

    def tsp_solve(network):
    """
    Solves the TSP using a genetic algorithm.
    :param network: list of nodes in the network
    :return: path, length of path
    """

    # Initialize population with random paths
    population = []

    for i in range(POPULATION_SIZE):
    population.append(random_path(network))

    # Calculate fitness of each path
    for i in range(POPULATION_SIZE):
    population[i][1] = fitness(population[i][0], network)

    # Sort paths by fitness
    population = sorted(population, key=lambda x: x[1])

    # Evolve the population
    for i in range(GENERATIONS):
    # Select parents for mating
    parents = []

    for j in range(PARENTS_COUNT):
    parents.append(population[random.randint(0, POPULATION_SIZE - 1)])

    # Crossover
    children = crossover(parents)

    # Mutation
    for j in range(len(children)):
    children[j] = mutation(children[j])

    # Calculate fitness of each path
    for j in range(len(children)):
    children[j][1] = fitness(children[j][0], network)

    # Sort paths by fitness
    children = sorted(children, key=lambda x: x[1])

    # Replace least fit population with new children
    for j in range(POPULATION_SIZE):
    population[j] = children[j]

    # Return most fit path
    return population[0]


    def random_path(network):
    """
    Generates a random path through the network.
    :param network: list of nodes in the network
    :return: list of nodes in the path, length of path
    """

    # Initialize variables
    path = []
    length = 0

    # Add first node to path
    path.append(network[0])

    # Add remaining nodes to path
    for i in range(len(network) - 1):
    node = network[random.randint(0, len(network) - 1)]

    # Make sure node is not already in path
    while node in path:
    node = network[random.randint(0, len(network) - 1)]

    path.append(node)
    length += distance(path[i], path[i + 1])

    # Add distance from last node to first node
    length += distance(path[len(path) - 1], path[0])

    # Return path and length
    return [path, length]


    def fitness(path, network):
    """
    Calculates the fitness of a path.
    :param path: list of nodes in the path
    :param network: list of nodes in the network
    :return: length of path
    """

    # Initialize variables
    length = 0

    # Add distances between consecutive nodes in path
    for i in range(len(path) - 1):
    length += distance(path[i], path[i + 1])

    # Add distance from last node to first node
    length += distance(path[len(path) - 1], path[0])

    # Return length
    return length


    def crossover(parents):
    """
    Generates children from a set of parents.
    :param parents: list of parent paths
    :return: list of children paths
    """

    # Initialize variables
    children = []

    # Generate children
    for i in range(len(parents) // 2):
    # Select parents
    parent1 = parents[random.randint(0, len(parents) - 1)]
    parent2 = parents[random.randint(0, len(parents) - 1)]

    # Select crossover point
    cross_point = random.randint(1, len(parent1[0]) - 1)

    # Generate children
    child1 = parent1[0][:cross_point] + parent2[0][cross_point:]
    child2 = parent2[0][:cross_point] + parent1[0][cross_point:]

    # Add children to list of children
    children.append([child1, 0])
    children.append([child2, 0])

    # Return list of children
    return children


    def mutation(path):
    """
    Mutates a path.
    :param path: list of nodes in the path
    :return: mutated path
    """

    # Initialize variables
    new_path = []

    # Select two points on the path to swap
    point1 = random.randint(0, len(path) - 1)
    point2 = random.randint(0, len(path) - 1)

    # Make sure points are not the same
    while point1 == point2:
    point2 = random.randint(0, len(path) - 1)

    # Swap nodes at the two points
    new_path = path[:point1] + path[point2:point2 + 1] + path[point1 + 1:point2] + path[point1:point1 + 1] + path[point2 + 1:]

    # Return mutated path
    return [new_path, 0]


    def distance(node1, node2):
    """
    Calculates the distance between two nodes.
    :param node1: first node
    :param node2: second node
    :return: distance between the nodes
    """

    # Return distance between nodes
    return math.sqrt((node1[0] - node2[0]) ** 2 + (node1[1] - node2[1]) ** 2)


    def main():
    """
    Main function.
    :return: None
    """

    # Initialize variables
    network = []
    path = []
    length = 0

    # Generate some random test data
    for i in range(100):
    network.append([random.randint(0, 100), random.randint(0, 100)])

    # Solve TSP using a genetic algorithm
    path, length = tsp_solve(network)

    # Print results
    print("Path:", path)
    print("Length:", length)

    # Plot the path and points with matplotlib.
    import matplotlib.pyplot as plt
    x = [p[0] for p in path]
    y = [p[1] for p in path]
    x.append(path[0][0])
    y.append(path[0][1])
    plt.plot(x, y)
    plt.scatter(x, y)
    plt.show()


    if __name__ == "__main__":
    main()