simanneal

Python module for Simulated Annealing optimization

https://github.com/perrygeo/simanneal

Science Score: 23.0%

This score indicates how likely this project is to be science-related based on various indicators:

  • CITATION.cff file
  • codemeta.json file
    Found codemeta.json file
  • .zenodo.json file
  • DOI references
  • Academic publication links
  • Committers with academic emails
    1 of 16 committers (6.3%) from academic institutions
  • Institutional organization owner
  • JOSS paper metadata
  • Scientific vocabulary similarity
    Low similarity (12.6%) to scientific vocabulary
Last synced: 10 months ago · JSON representation

Repository

Python module for Simulated Annealing optimization

Basic Info
  • Host: GitHub
  • Owner: perrygeo
  • License: isc
  • Language: Python
  • Default Branch: master
  • Homepage:
  • Size: 804 KB
Statistics
  • Stars: 673
  • Watchers: 24
  • Forks: 178
  • Open Issues: 11
  • Releases: 2
Created over 13 years ago · Last pushed about 2 years ago
Metadata Files
Readme Changelog License

README.md

Python module for simulated annealing

Build Status PyPI version

This module performs simulated annealing optimization to find the optimal state of a system. It is inspired by the metallurgic process of annealing whereby metals must be cooled at a regular schedule in order to settle into their lowest energy state.

Simulated annealing is used to find a close-to-optimal solution among an extremely large (but finite) set of potential solutions. It is particularly useful for combinatorial optimization problems defined by complex objective functions that rely on external data.

The process involves:

  1. Randomly move or alter the state
  2. Assess the energy of the new state using an objective function
  3. Compare the energy to the previous state and decide whether to accept the new solution or reject it based on the current temperature.
  4. Repeat until you have converged on an acceptable answer

For a move to be accepted, it must meet one of two requirements:

  • The move causes a decrease in state energy (i.e. an improvement in the objective function)
  • The move increases the state energy (i.e. a slightly worse solution) but is within the bounds of the temperature. The temperature exponetially decreases as the algorithm progresses. In this way, we avoid getting trapped by local minima early in the process but start to hone in on a viable solution by the end.

Example: Travelling Salesman Problem

The quintessential discrete optimization problem is the travelling salesman problem.

Given a list of locations, what is the shortest possible route that hits each location and returns to the starting city?

To put it in terms of our simulated annealing framework: * The state is an ordered list of locations to visit * The move shuffles two cities in the list * The energy of a give state is the distance travelled

Quickstart

Install it first ```bash pip install simanneal # from pypi

pip install -e git+https://github.com/perrygeo/simanneal.git # latest from github ```

To define our problem, we create a class that inherits from simanneal.Annealer

python from simanneal import Annealer class TravellingSalesmanProblem(Annealer): """Test annealer with a travelling salesman problem."""

Within that class, we define two required methods. First, we define the move:

python def move(self): """Swaps two cities in the route.""" a = random.randint(0, len(self.state) - 1) b = random.randint(0, len(self.state) - 1) self.state[a], self.state[b] = self.state[b], self.state[a]

Then we define how energy is computed (also known as the objective function): python def energy(self): """Calculates the length of the route.""" e = 0 for i in range(len(self.state)): e += self.distance(cities[self.state[i - 1]], cities[self.state[i]]) return e

Note that both of these methods have access to self.state which tracks the current state of the process.

So with our problem specified, we can construct a TravellingSalesmanProblem instance and provide it a starting state

python initial_state = ['New York', 'Los Angeles', 'Boston', 'Houston'] tsp = TravellingSalesmanProblem(initial_state)

And run it python itinerary, miles = tsp.anneal()

See examples/salesman.py to see the complete implementation.

Annealing parameters

Getting the annealing algorithm to work effectively and quickly is a matter of tuning parameters. The defaults are: python Tmax = 25000.0 # Max (starting) temperature Tmin = 2.5 # Min (ending) temperature steps = 50000 # Number of iterations updates = 100 # Number of updates (by default an update prints to stdout) These can vary greatly depending on your objective function and solution space.

A good rule of thumb is that your initial temperature Tmax should be set to accept roughly 98% of the moves and that the final temperature Tmin should be low enough that the solution does not improve much, if at all.

The number of steps can influence the results; if there are not enough iterations to adequately explore the search space it can get trapped at a local minimum.

The number of updates doesn't affect the results but can be useful for examining the progress. The default update method (Annealer.update) prints a table to stdout and includes the current temperature, state energy, the percentage of moves accepted and improved and elapsed and remaining time. You can override .update and provide your own custom reporting mechanism to e.g. graphically plot the progress.

If you want to specify them manually, the are just attributes of the Annealer instance. python tsp.Tmax = 12000.0 ... However, you can use the .auto method which attempts to explore the search space to determine some decent starting values and assess how long each iteration takes. This allows you to specify roughly how long you're willing to wait for results.

```python auto_schedule = tsp.auto(minutes=1)

{'tmin': ..., 'tmax': ..., 'steps': ...}

tsp.setschedule(autoschedule) itinerary, miles = tsp.anneal() ```

Extra data dependencies

You might have noticed that the energy function above requires a cities dict that is presumably defined in the enclosing scope. This is not necessarily a good design pattern. The dependency on additional data can be made explicit by passing them into the constructor like so

# pass extra data (the distance matrix) into the constructor
def __init__(self, state, distance_matrix):
    self.distance_matrix = distance_matrix
    super(TravellingSalesmanProblem, self).__init__(state)  # important!

The last line (calling __init__ on the super class) is critical.

Optimizations

For some problems the energy function is prohibitively expensive to calculate after every move. It is often possible to compute the change in energy that a move causes much more efficiently.

For this reason, a non-None value returned from move will be treated as a delta and added to the previous energy value to get the energy value for the current move. If your model allows you to cheaply calculate a change in energy from the previous state, this approach will save you a call to energy. It is fine for the move operation to sometimes return a delta value and sometimes return None, depending on the type of modification it makes to the state and the complexity of calculting a delta.

Implementation Details

The simulated annealing algorithm requires that we track states (current, previous, best), which means we need to copy self.state frequently.

Copying an object in Python is not always straightforward or performant. The standard library provides a copy.deepcopy() method to copy arbitrary python objects but it is very expensive. Certain objects can be copied by more efficient means: lists can be sliced and dictionaries can use their own .copy method, etc.

In order to facilitate flexibility, you can specify the copy_strategy attribute which defines one of: * deepcopy: uses copy.deepcopy(object) * slice: uses object[:] * method: uses object.copy()

If you want to implement your own custom copy mechanism, override the copy_state method.

Notes

  1. Thanks to Richard J. Wagner at University of Michigan for writing and contributing the bulk of this code.
  2. Some effort has been made to increase performance but this is nowhere near as fast as optimized solutions written in other low-level languages. On the other hand, this is a very flexible, Python-based solution that can be used for rapidly experimenting with a computational problem with minimal overhead.
  3. Using PyPy instead of CPython can yield substantial increases in performance.
  4. For certain problems, there are simulated annealing techniques that are highly customized and optimized for the particular domain
    • For conservation planning, check out Marxan which is designed to prioritize conservation resources according to multiple planning objectives
    • For forest management and timber harvest scheduling, check out Harvest Scheduler which optimizes forestry operations over space and time to meet multiple objectives.
  5. Most times, you'll want to run through multiple repetions of the annealing runs. It is helpful to examine the states between 20 different runs. If the same or very similar state is acheived 20 times, it's likely that you've adequeately converged on a nearly-optimal answer.

Owner

  • Name: Matthew Perry
  • Login: perrygeo
  • Kind: user
  • Location: Fort Collins, CO

Geospatial Data Engineer, using ML and Remote Sensing to build high-quality forest carbon credits @pachama

GitHub Events

Total
  • Issues event: 1
  • Watch event: 38
  • Issue comment event: 2
  • Fork event: 1
Last Year
  • Issues event: 1
  • Watch event: 38
  • Issue comment event: 2
  • Fork event: 1

Committers

Last synced: about 1 year ago

All Time
  • Total Commits: 67
  • Total Committers: 16
  • Avg Commits per committer: 4.188
  • Development Distribution Score (DDS): 0.388
Past Year
  • Commits: 0
  • Committers: 0
  • Avg Commits per committer: 0.0
  • Development Distribution Score (DDS): 0.0
Top Committers
Name Email Commits
Matthew Perry p****o@g****m 41
Iain i****n@i****k 5
Ahmed Kamal e****l@g****m 2
Benoudjit Hakim h****t@g****m 2
Sarah Lutteropp s****p@g****m 2
matsulib t****u@g****m 2
terrycojones t****y@j****s 2
Jared Deckard j****d@g****m 2
Matthew Perry m****y@e****g 2
Alessandro Motta a****1@g****m 1
Gunnstein g****s@g****m 1
Tarcísio Fischer t****o@g****m 1
Yuta Hinokuma h****n 1
neex n****l@g****m 1
Andreas Straninger a****r@g****m 1
Andrew Giessel a****l@m****m 1
Committer Domains (Top 20 + Academic)

Issues and Pull Requests

Last synced: 11 months ago

All Time
  • Total issues: 22
  • Total pull requests: 29
  • Average time to close issues: 5 months
  • Average time to close pull requests: 2 months
  • Total issue authors: 17
  • Total pull request authors: 22
  • Average comments per issue: 1.45
  • Average comments per pull request: 0.79
  • Merged pull requests: 20
  • Bot issues: 0
  • Bot pull requests: 0
Past Year
  • Issues: 1
  • Pull requests: 0
  • Average time to close issues: N/A
  • Average time to close pull requests: N/A
  • Issue authors: 1
  • Pull request authors: 0
  • Average comments per issue: 2.0
  • Average comments per pull request: 0
  • Merged pull requests: 0
  • Bot issues: 0
  • Bot pull requests: 0
Top Authors
Issue Authors
  • perrygeo (4)
  • matsulib (2)
  • DonGiulio (2)
  • matthieu-pa (1)
  • DataScienceUWL (1)
  • straningera (1)
  • uqhwen2 (1)
  • francisco-simoes (1)
  • ZhiWeiCui (1)
  • chuanqiWen (1)
  • shane328 (1)
  • terrycojones (1)
  • exepulveda (1)
  • weullerk (1)
  • atefeh-mehrabi (1)
Pull Request Authors
  • perrygeo (4)
  • adolenc (3)
  • kim0 (2)
  • lutteropp (2)
  • deckar01 (2)
  • andrewgiessel (1)
  • cankish (1)
  • matsulib (1)
  • tarcisiofischer (1)
  • Gunnstein (1)
  • amotta (1)
  • terrycojones (1)
  • Iain-S (1)
  • Alex-Linhares (1)
  • neex (1)
Top Labels
Issue Labels
bug (1) help wanted (1) enhancement (1)
Pull Request Labels

Packages

  • Total packages: 10
  • Total downloads:
    • pypi 10,879 last-month
  • Total docker downloads: 189
  • Total dependent packages: 7
    (may contain duplicates)
  • Total dependent repositories: 44
    (may contain duplicates)
  • Total versions: 25
  • Total maintainers: 2
pypi.org: simanneal

Simulated Annealing in Python

  • Versions: 10
  • Dependent Packages: 7
  • Dependent Repositories: 43
  • Downloads: 10,879 Last month
  • Docker Downloads: 189
Rankings
Dependent packages count: 1.4%
Dependent repos count: 2.2%
Stargazers count: 2.6%
Average: 3.1%
Docker downloads count: 3.6%
Forks count: 3.8%
Downloads: 4.9%
Maintainers (1)
Last synced: 11 months ago
alpine-v3.18: py3-simanneal

Python module for Simulated Annealing optimization algorithm

  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
Rankings
Dependent repos count: 0.0%
Dependent packages count: 0.0%
Average: 4.7%
Forks count: 8.0%
Stargazers count: 10.7%
Maintainers (1)
Last synced: 11 months ago
alpine-v3.18: py3-simanneal-pyc

Precompiled Python bytecode for py3-simanneal

  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
Rankings
Dependent repos count: 0.0%
Dependent packages count: 0.0%
Average: 4.7%
Forks count: 8.0%
Stargazers count: 10.7%
Maintainers (1)
Last synced: 11 months ago
alpine-edge: py3-simanneal-pyc

Precompiled Python bytecode for py3-simanneal

  • Versions: 3
  • Dependent Packages: 0
  • Dependent Repositories: 0
Rankings
Dependent repos count: 0.0%
Average: 8.8%
Forks count: 9.0%
Stargazers count: 12.7%
Dependent packages count: 13.3%
Maintainers (1)
Last synced: 11 months ago
alpine-edge: py3-simanneal

Python module for Simulated Annealing optimization algorithm

  • Versions: 4
  • Dependent Packages: 0
  • Dependent Repositories: 0
Rankings
Dependent repos count: 0.0%
Forks count: 8.7%
Average: 8.9%
Stargazers count: 12.2%
Dependent packages count: 14.6%
Maintainers (1)
Last synced: 11 months ago
conda-forge.org: simanneal
  • Versions: 2
  • Dependent Packages: 0
  • Dependent Repositories: 1
Rankings
Forks count: 13.5%
Stargazers count: 16.7%
Dependent repos count: 24.3%
Average: 26.5%
Dependent packages count: 51.6%
Last synced: 11 months ago
alpine-v3.19: py3-simanneal-pyc

Precompiled Python bytecode for py3-simanneal

  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
Rankings
Dependent repos count: 0.0%
Dependent packages count: 0.0%
Average: 100%
Maintainers (1)
Last synced: 11 months ago
alpine-v3.19: py3-simanneal

Python module for Simulated Annealing optimization algorithm

  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
Rankings
Dependent repos count: 0.0%
Dependent packages count: 0.0%
Average: 100%
Last synced: 11 months ago
alpine-v3.20: py3-simanneal-pyc

Precompiled Python bytecode for py3-simanneal

  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
Rankings
Dependent repos count: 0.0%
Dependent packages count: 0.0%
Average: 100%
Maintainers (1)
Last synced: 11 months ago
alpine-v3.20: py3-simanneal

Python module for Simulated Annealing optimization algorithm

  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
Rankings
Dependent repos count: 0.0%
Dependent packages count: 0.0%
Average: 100%
Maintainers (1)
Last synced: 11 months ago

Dependencies

requirements-dev.txt pypi
  • pytest * development
  • pytest-coverage * development
setup.py pypi