Anneal - Snailz Docs

Skip to content

# Anneal

Simulated annealing to place snailz.

## `anneal(size, specimens)`

Calculate specimen positions using simulated annealing.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`size` | `int` | grid size | required
`specimens` | `Sequence[MinimalSpecimen]` | partially-initialized specimens to place | required

Source code in `src/snailz/anneal.py`

    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    def anneal(size: int, specimens: Sequence[MinimalSpecimen]) -> None:
        """Calculate specimen positions using simulated annealing.

        Parameters:
            size: grid size
            specimens: partially-initialized specimens to place
        """
        _initial_placement(size, specimens)
        grid = _make_grid(size, specimens)
        wall = _make_wall(size)
        for i in range(STEPS):
            _move(grid, wall, specimens)

## `_initial_placement(size, specimens)`

Randomly initialize specimen placement.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`size` | `int` | grid size | required
`specimens` | `Sequence[MinimalSpecimen]` | partially-initialized specimens to place | required

Source code in `src/snailz/anneal.py`

    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    def _initial_placement(size: int, specimens: Sequence[MinimalSpecimen]) -> None:
        """Randomly initialize specimen placement.

        Parameters:
            size: grid size
            specimens: partially-initialized specimens to place
        """
        candidates = [(x, y) for x in range(size) for y in range(size)]
        for i, (x, y) in enumerate(random.sample(candidates, len(specimens))):
            specimens[i].location.x = x
            specimens[i].location.y = y

## `_make_grid(size, specimens)`

Make initial grid to track specimen positions during placement.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`size` | `int` | grid size | required
`specimens` | `Sequence[MinimalSpecimen]` | partially-initialized specimens to place | required

Source code in `src/snailz/anneal.py`

    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    def _make_grid(size: int, specimens: Sequence[MinimalSpecimen]) -> Grid[str]:
        """Make initial grid to track specimen positions during placement.

        Parameters:
            size: grid size
            specimens: partially-initialized specimens to place
        """
        grid = Grid(width=size, height=size, default="")
        for s in specimens:
            grid[s.location.x, s.location.y] = s.ident
        return grid

## `_make_wall(size)`

Make a wall of unit-mass specimens to keep actual specimens in the grid.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`size` | `int` | grid size | required

Source code in `src/snailz/anneal.py`

    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    def _make_wall(size: int) -> list[MinimalSpecimen]:
        """Make a wall of unit-mass specimens to keep actual specimens in the grid.

        Parameters:
            size: grid size
        """
        result = []
        bounds = (-BORDER, size + BORDER - 1)
        for x in range(*bounds):
            for y in bounds:
                result.append(
                    MinimalSpecimen(
                        ident="",
                        location=Point(x=x, y=y),
                        mass=1.0,
                    )
                )
        for y in range(1 - BORDER, size + BORDER - 2):
            for x in bounds:
                result.append(
                    MinimalSpecimen(
                        ident="",
                        location=Point(x=x, y=y),
                        mass=1.0,
                    )
                )
        return result

## `_move(grid, wall, specimens)`

Move a randomly-selected specimen one step.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`grid` | `Grid[str]` | temporary grid showing specimen positions | required
`wall` | `Sequence[MinimalSpecimen]` | barrier around outside of grid | required
`specimens` | `Sequence[MinimalSpecimen]` | specimens being moved | required

Source code in `src/snailz/anneal.py`

     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    def _move(
        grid: Grid[str],
        wall: Sequence[MinimalSpecimen],
        specimens: Sequence[MinimalSpecimen],
    ) -> None:
        """Move a randomly-selected specimen one step.

        Parameters:
            grid: temporary grid showing specimen positions
            wall: barrier around outside of grid
            specimens: specimens being moved
        """
        i = random.randint(0, len(specimens) - 1)
        s = specimens[i]
        f_point_x, f_point_y = _point_point_force(specimens, i)
        f_wall_x, f_wall_y = _wall_point_force(wall, s)
        new_x = _clip(grid.width, s.location.x, f_point_x + f_wall_x)
        new_y = _clip(grid.height, s.location.y, f_point_y + f_wall_y)
        if grid[new_x, new_y] == "":
            grid[s.location.x, s.location.y] = ""
            s.location.x = new_x
            s.location.y = new_y
            grid[s.location.x, s.location.y] = s.ident

## `_point_point_force(specimens, i)`

Calculate force on specimen 'i' from other specimens.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`specimens` | `Sequence[MinimalSpecimen]` | all specimens | required
`i` | `int` | which specimen is being moved | required

Returns:

Type | Description
--- | ---
`tuple[float, float]` | XY components of force.

Source code in `src/snailz/anneal.py`

    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    def _point_point_force(
        specimens: Sequence[MinimalSpecimen], i: int
    ) -> tuple[float, float]:
        """Calculate force on specimen 'i' from other specimens.

        Parameters:
            specimens: all specimens
            i: which specimen is being moved

        Returns:
            XY components of force.
        """
        fx, fy = 0.0, 0.0
        for j, s in enumerate(specimens):
            if i == j:
                continue
            dx, dy = _single_force(specimens[i], s)
            fx += dx
            fy += dy
        return fx, fy

## `_wall_point_force(wall, specimen)`

Calculate force on selected specimen from fixed wall.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`wall` | `Sequence[MinimalSpecimen]` | fixed wall containing specimens | required
`specimen` | `MinimalSpecimen` | specimen being moved | required

Returns:

Type | Description
--- | ---
`tuple[float, float]` | XY components of force.

Source code in `src/snailz/anneal.py`

    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    def _wall_point_force(
        wall: Sequence[MinimalSpecimen], specimen: MinimalSpecimen
    ) -> tuple[float, float]:
        """Calculate force on selected specimen from fixed wall.

        Parameters:
            wall: fixed wall containing specimens
            specimen: specimen being moved

        Returns:
            XY components of force.
        """
        fx, fy = 0.0, 0.0
        for w in wall:
            dx, dy = _single_force(specimen, w)
            fx += dx
            fy += dy
        return fx, fy

## `_single_force(s0, s1)`

Calculate force on specimen from another specimen.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`s0` | `MinimalSpecimen` | specimen being moved | required
`s1` | `MinimalSpecimen` | specimen acting on it | required

Returns:

Type | Description
--- | ---
`tuple[float, float]` | XY components of force.

Source code in `src/snailz/anneal.py`

    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    def _single_force(s0: MinimalSpecimen, s1: MinimalSpecimen) -> tuple[float, float]:
        """Calculate force on specimen from another specimen.

        Parameters:
            s0: specimen being moved
            s1: specimen acting on it

        Returns:
            XY components of force.
        """
        loc0 = s0.location
        loc1 = s1.location
        dx = loc1.x - loc0.x
        dy = loc1.y - loc0.y
        r_sq = dx**2 + dy**2
        assert r_sq > 0, f"{s0} vs. {s1}"
        r = math.sqrt(r_sq)
        f = s0.mass * s1.mass / r_sq
        fx = -f * dx / r
        fy = -f * dy / r
        return fx, fy

## `_clip(size, coord, force)`

Calculate new coordinate (old-1, old+1, or 0 depending on force).

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`size` | `int` | grid size | required
`coord` | `int` | X or Y coordinate | required
`force` | `float` | force in that direction | required

Returns:

Type | Description
--- | ---
`int` | New coordinate.

Source code in `src/snailz/anneal.py`

    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    def _clip(size: int, coord: int, force: float) -> int:
        """Calculate new coordinate (old-1, old+1, or 0 depending on force).

        Parameters:
            size: grid size
            coord: X or Y coordinate
            force: force in that direction

        Returns:
            New coordinate.
        """
        if (force < 0) and (coord > 0):
            return coord - 1
        if (force > 0) and (coord < size - 1):
            return coord + 1
        return coord