simanneal icon indicating copy to clipboard operation
simanneal copied to clipboard

Avoid copies if it is possible to undo a move

Open adolenc opened this issue 9 months ago • 0 comments

For states with complex objects, copies can take a significant bulk of the time spent by the algorithm. This pull requests add optional support for defining def undo_move(self) method on the class, which the user can implement to undo the last move, almost avoiding the copying in the body of the annealing.

For demonstration purposes consider the following (admittedly convoluted) example:

import random
from simanneal import Annealer

class Queen:
    def __init__(self, row):
        self.row = row
        self.dummy1 = set(range(12))
        self.dummy2 = {i:i for i in range(15)}
        self.dummy3 = {i:i for i in range(20)}


class NQueensProblem(Annealer):
    def move(self):
        """Move a queen on a random column to some different row."""
        column = random.randrange(len(self.state))
        new_row = random.randrange(len(self.state))

        self.last_move = (column, self.state[column].row)
        self.state[column].row = new_row

    def undo_move(self):
        self.state[self.last_move[0]].row = self.last_move[1]

    def energy(self):
        """Calculates the number of attacks among the queens."""
        e = 0
        for i in range(len(self.state)):
            for j in range(i + 1, len(self.state)):
                e += self.state[i].row == self.state[j].row
                e += abs(i - j) == abs(self.state[i].row - self.state[j].row)
        return e


if __name__ == '__main__':
    nqueens = NQueensProblem([Queen(row) for row in range(20)])
    nqueens.Tmax = 100
    nqueens.Tmin = 0.1
    nqueens.steps = 500000
    state, e = nqueens.anneal()

    print()
    print(f"number of attacks: {e}")
    print([s.row for s in state])

On my pc, this code runs approximately 20x slower when I comment out undo_move (3 minutes vs 10 seconds).

adolenc avatar May 03 '24 18:05 adolenc