Coder Social home page Coder Social logo

simanneal's Introduction

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

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

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:

    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):

    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

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

And run it

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:

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.

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.

auto_schedule = tsp.auto(minutes=1) 
# {'tmin': ..., 'tmax': ..., 'steps': ...}

tsp.set_schedule(auto_schedule)
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.

simanneal's People

Contributors

amotta avatar gunnstein avatar h4k1m0u avatar higumachan avatar kim0 avatar lutteropp avatar matsulib avatar neex avatar perrygeo avatar tarcisiofischer avatar terrycojones avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

simanneal's Issues

Litterature reference for simulated annealing

Thank you so much for providing an amazing package.

I want to know which literature you have referenced to implement the simulated annealing algorithm.

Because I checked the original related literature of this algorithm, it usually contains two loops: an inner loop and an outer loop.

I want to know more about this literature .

E.g: Gendreau, Michel, Jean-Yves Potvin, and others. Handbook of Metaheuristics. Vol. 2. Springer, 2010.

Snipaste_2021-04-16_15-57-33

Dependency injection for external data

Currently, if your objective function relies on any external data it must be defined in the enclosing scope. It would be nice to allow required data to be specified with __init__. Maybe that just means testing and documenting this technique...

class Optimizer(Annealer):
    def __init__(self, state, moredata, extradata):
        self.moredata = moredata
        self.extradata = extradata
        super(Optimizer, self).__init__(state)

question on multi-objective

Hi,
Does this code support multi-objective simulated annealing when objectives are multiple different independent functions? Is there any sample or hint on which parts of anneal.py should be modified? I would appreciate your guidance on this and thanks for sharing this repository.

copy_state() returns None

copy_state() returns None when self.copy_strategy is mistakenly set up.
Then the error message is not easy to understand.

For example, when I set tsp.copy_strategy = "copy" instead of "slice" in example/salesman.py,
the output was like this.

$ python examples/salesman.py
 Temperature        Energy    Accept   Improve     Elapsed   Remaining
Traceback (most recent call last):                 0:00:00
  File "examples/salesman.py", line 88, in <module>
    state, e = tsp.anneal()
  File "../simanneal/simanneal/anneal.py", line 192, in anneal
    self.move()
  File "examples/salesman.py", line 31, in move
    a = random.randint(0, len(self.state) - 1)
TypeError: object of type 'NoneType' has no len()

It took time to know where the 'NoneType' came from.

It would be useful if we can see the cause is tsp.copy_strategy.

Unit tests

How do you unit test a stochastic process? ... need to do some research.

Speeding Up the Calculation

I'm wondering how can we do to leverage multiple CPUs when doing the annealing algorithm? I try to use GPU for quicker calculation, but it seems does not offer any help :(.

I notice that when I run this code, it only takes up 1 CPU, but my other learning algorithm like simple neural network can take up multiple CPUs to accelerate the calculation.

Thanks for any reply.

Cheers.

Temperature and Energy stuck at zero

Hi!
When I run the optimization algorithm, the table gets stuck like this:
image
I've seen a similar issue, but it was related to PyCharm and I use Emacs.

Changing the update method so that it simply prints *args and **kwargs simply prints 0 0.0 0.0 None None once. So it seems the code is stuck at the first iteration. The code keeps running even after half an hour or more, even though I used auto(minutes=0.2).
Further, adding some prints to my energy function shows that the energy function is being called over and over. Its value does change, and so do the state parameters.

Thanks!

Mixin for plotting updates

class Optimizer(Annealer, AnnealGraph):
    ...

Would override the .update method to generate a matplotlib plot to graphically show the progress of the simulated annealing.

Pass current temperature to move function

Hi
I think it would be a good thing to have access to the current temperature in the move function, either by passing it as a parameter or by making it a class member.
It would allow for smaller changes in the state when the temperature is small for example

Allow any type of state variable

I could use type numpy.ndarray or pandas.DataFrame for state variable from #12.
Unfortunately, the patch of #12 was canceled by ed97524.
There seems to be a little confusion.

I have a suggestion about this code.

class Annealer(object):
    def __init__(self, initial_state=None, load_state=None):
        if initial_state:
            self.state = self.copy_state(initial_state)
        elif load_state:
            with open(load_state, 'rb') as fh:
                self.state = pickle.load(fh)
        else:
            raise ValueError('No valid values supplied for neither \
            initial_state nor load_state')

To start annealing, I think only two things are required in __init__().
First, either initial_state or load_state is given.
Second, the given object is copyable. The definition of "copyable" can be custmized by overriding copy_state().

Therefore, all we have to do in __init__() is to check whether initial_state is given or not.
Other conditions about state can be handled by move() and energey(), so it doesn't matter if initial_state itself is evaluated as false like an empty list.

multithreading

This algorithm can be quite time consuming, and it could benefit greatly from multithreading,

I'm not sure how this could be achieved, in sight of the fact that the original algorithm considers just one solution per time, maybe, because the next moves are independent from the next temperature, one could calculate some of the next moves, so that the results might come much faster.

of course there might be some wasted computation, but there would be a good improvement on running speed regardless.

your thoughts?
Thanks

Replacing lines to shorten output

Hi. I'm using the simulated annealing part several times in my scripts. The console output is too long for my task, so I changed the print statements in lines 86 et seqq. to the following:

        sys.stdout.write('\r%12.2f  %12.2f                      %s            ' % \
            (T, E, time_string(elapsed)))
        sys.stdout.flush()

and

        sys.stdout.write('\r%12.2f  %12.2f  %7.2f%%  %7.2f%%  %s  %s' % \
            (T, E, 100.0 * acceptance, 100.0 * improvement,
                time_string(elapsed), time_string(remain))),
        sys.stdout.flush()

By doing so, previous outputs are replaced by the most recent output.

tsp example move() needs a fix

self.move() in the the tsp example returns self.energy() or E, but should return dE shouldn't it?

it took me a while to track this down, but anneal() blows up when self.move() returns E instead of dE.

Problem in code

In method, there is a problem:
def update(self, *args, **kwargs):
self.default_update(self,*args, **kwargs)

It should be:
def update(self, *args, **kwargs):
self.default_update(*args, **kwargs)

updates not printed out

hello,
I'm trying out simanneal, and found something that I don't understand:

here's my code:

oa = OptimizerAnnealer(strategy, ranges, self.get_data())
configuration, points = oa.anneal()

where my annealer is:

def __init__(self, strategy, ranges, data):
    self.strategy = strategy
    self.ranges = ranges
    self.data = data

    # initial state with first value of each range
    self.state = {}
    for key, value in self.ranges.items():
        self.state[key] = value[0]

    super(OptimizerAnnealer, self).__init__(self.state)  # important!

def move(self):
    key = pick(list(self.state))
    new_value = following(list(self.ranges[key]), self.state[key])
    self.state[key] = new_value

def energy(self):
   # energy function calculates points related to current state, obtaining a `result` float
   # that depends on state. 
    return result

the output of simanneal is:

Temperature        Energy    Accept   Improve     Elapsed   Remaining
 25000.00000        616.99                         0:00:02

and that's it, it doesn't show any updates at all. even though there are new solutions that have lower energy level.

anything I'm doing wrong?

Thanks

ZeroDivisionError in anneal.py

I'm not sure how this can happen, but I'm looking at it in my terminal output, so I can at least say that it did happen!

  File "/home/terry/s/net/simanneal/simanneal/anneal.py", line 211, in anneal
    if dE > 0.0 and math.exp(-dE / T) < random.random():
ZeroDivisionError: float division by zero

So it seems T=0, which, from code about 10 lines above, would mean that self.Tmax would need to be zero. I'm running using the auto method and set_schedule. So maybe when auto sets Tmax to be the result of running round_figures the value is zero?

.auto() yields tmax == tmin, and negative remaining time

Running .auto() in a script output the following

 Temperature        Energy    Accept   Improve     Elapsed   Remaining
        0.21         21.00   100.00%     0.00%     0:00:10    -1:59:50
{u'tmax': 0.21, u'tmin': 0.21, u'steps': 310000000.0}

I'm new to all of this .. Just wondering if this is a bug, or if it can make sense sometimes ?

Litterature reference for auto scheduling

First thank you for this implementation of SA! Its flexibility allowed me to get really good results in a few hours on the Quadratic Knapsack Problem by implementing two simple moves.

I was wondering if the auto scheduling from below is something you came up on your own or something which can be found in the literature? If it is from the literature, could you give me the references?

def auto(self, minutes, steps=2000):

Thank you for your time.

Is it possible to perform a continuous problem with this package?

I'm a very beginner in Python and want to know if it's possible to do an optimization for n-dimensions Rosenbrock's function with this package

It's sad that the part of "class TravellingSalesmanProblem" in example is too difficult for me to understand and therefore couldn't edit it as a suitable form for continuous problem

Here is an example I wrote to define 3D-Rosenbrock's function and the initial solution, initial objective value for SA:

`def rosen(x):
"""The Rosenbrock function"""
return sum(100.0*(x[1:]-x[:-1]**2.0)**2.0 + (1-x[:-1])**2.0)

Initial solution you'd like to start at

X0 = np.array([2, 1, 1.5])

Initial objective value of Rosenbrock's test function

of_int = rosen(X0)`

Hope someone could help me with an easy example!
Thanks in advance!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.