{"id":21849251,"url":"https://github.com/pasaopasen/simplestsimulatedannealing","last_synced_at":"2025-03-21T17:43:07.996Z","repository":{"id":57467676,"uuid":"319467869","full_name":"PasaOpasen/SimplestSimulatedAnnealing","owner":"PasaOpasen","description":"Simplest fast implementation of simulated annlealing method","archived":false,"fork":false,"pushed_at":"2022-04-13T19:59:26.000Z","size":1903,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-26T20:03:28.255Z","etag":null,"topics":["evolution","evolution-simulation","evolutionary-algorithm","genetic-algorithm","optimization","optimization-algorithms","optimization-methods","pypi-package","python","simulated-annealing"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/PasaOpasen.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-12-07T23:09:37.000Z","updated_at":"2024-08-06T08:11:29.000Z","dependencies_parsed_at":"2022-09-17T16:12:08.767Z","dependency_job_id":null,"html_url":"https://github.com/PasaOpasen/SimplestSimulatedAnnealing","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PasaOpasen%2FSimplestSimulatedAnnealing","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PasaOpasen%2FSimplestSimulatedAnnealing/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PasaOpasen%2FSimplestSimulatedAnnealing/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PasaOpasen%2FSimplestSimulatedAnnealing/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/PasaOpasen","download_url":"https://codeload.github.com/PasaOpasen/SimplestSimulatedAnnealing/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244842599,"owners_count":20519654,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["evolution","evolution-simulation","evolutionary-algorithm","genetic-algorithm","optimization","optimization-algorithms","optimization-methods","pypi-package","python","simulated-annealing"],"created_at":"2024-11-28T00:12:00.960Z","updated_at":"2025-03-21T17:43:07.954Z","avatar_url":"https://github.com/PasaOpasen.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![PyPI\nversion](https://badge.fury.io/py/SimplestSimulatedAnnealing.svg)](https://pypi.org/project/SimplestSimulatedAnnealing/)\n[![Downloads](https://pepy.tech/badge/SimplestSimulatedAnnealing)](https://pepy.tech/project/SimplestSimulatedAnnealing)\n[![Downloads](https://pepy.tech/badge/SimplestSimulatedAnnealing/month)](https://pepy.tech/project/SimplestSimulatedAnnealing)\n[![Downloads](https://pepy.tech/badge/SimplestSimulatedAnnealing/week)](https://pepy.tech/project/SimplestSimulatedAnnealing)\n\n# Simplest simulated annealing\n\nSimplest implementation (from [DPEA](https://github.com/PasaOpasen/PasaOpasen.github.io/blob/master/EA_packages.md)) of simulated annealing method\n\n```\npip install SimplestSimulatedAnnealing\n```\n\n- [Simplest simulated annealing](#simplest-simulated-annealing)\n  - [Idea of method](#idea-of-method)\n  - [Simple usage](#simple-usage)\n  - [Parameters of method](#parameters-of-method)\n  - [Temperature regimes](#temperature-regimes)\n    - [Pattern](#pattern)\n    - [Available functions](#available-functions)\n    - [Plot temperature](#plot-temperature)\n    - [Difference between coolings](#difference-between-coolings)\n    - [Multiple coolings](#multiple-coolings)\n    - [Give a change to multiple coolings](#give-a-change-to-multiple-coolings)\n  - [About mutation](#about-mutation)\n- [Examples](#examples)\n  - [Select best subset](#select-best-subset)\n  - [Do best split](#do-best-split)\n  - [Travelling salesman problem](#travelling-salesman-problem)\n\n## Idea of method\n\nThis is the evolutionary algorithm for *function minimization*. \n\nSteps:\n1. We should determine function `f` must be minimized\n2. Determine start solution `x0` (can be random)\n3. Determine mutation function `mut`. This function should give new (can be random) `x1` solution using information about `x0` and temperature `T`.\n4. Select or create `cooling` regime(s) (temperature behavior)\n5. Set start temperature(s) `T`\n6. Run searching:\n   * at start we have `x0` solution and `f(x0)` best score\n   * let's create mutant `x1 = mut(x0)` and calculate `f(x1)`\n   * if `f(x1) \u003c f(x0)`, we found better solution `x0 = x1`. Otherwise we can replace `x1` with `x0` with probability equals `exp((f(x0) - f(x1)) / T)`\n   * decrease `T` using `cooling` function: `T = cooling(T)`\n   * repeat last 3 steps until stop criterion \n\n## Simple usage\n\nImport packages:\n\n```python\nimport math\nimport numpy as np\n\nfrom SimplestSimulatedAnnleaning import SimulatedAnnealing, Cooling, simple_continual_mutation\n```\n\nDetermine minimized function (Rastrigin):\n\n```python\ndef Rastrigin(arr):\n    return 10*arr.size+np.sum(arr**2) - 10*np.sum(np.cos(2*math.pi*arr))\n\ndim = 5\n```\nWe will use simplest gauss mutation:\n\n```python\nmut = simple_continual_mutation(std = 0.5)\n```\n\nCreate model object (set function and dimension):\n\n```python\nmodel = SimulatedAnnealing(Rastrigin, dim)\n```\n\nStart searching and see report:\n\n```python\nbest_solution, best_val = model.run(\n    start_solution = np.random.uniform(-5, 5, dim),\n    mutation = mut,\n    cooling = Cooling.exponential(0.9), \n    start_temperature = 100, \n    max_function_evals = 1000, \n    max_iterations_without_progress = 100, \n    step_for_reinit_temperature = 80\n    )\n\nmodel.plot_report(save_as = 'simple_example.png')\n```\n\n![](tests/simple_example.png)\n\n## Parameters of method\n\nMain method of the package is `run()`. Let's check it's arguments:\n\n```python\nmodel.run(start_solution, \n          mutation, \n          cooling, \n          start_temperature, \n          max_function_evals = 1000, \n          max_iterations_without_progress = 250, \n          step_for_reinit_temperature = 90,\n          reinit_from_best = False,\n          seed = None)\n```\n\nWhere:\n* `start_solution` : numpy array; solution from which it should start.\n* `mutation` : function (array, array/number). Function like\n  ```python\n  def mut(x_as_array, temperature_as_array_or_one_number):\n      # some code\n      return new_x_as_array\n  ```\n    This function will create new solutions from existing. [See also](#about-mutation)\n\n* `cooling` : cooling function / functions list. Cooling function or a list of ones. [See](#temperature-regimes)\n\n* `start_temperature` : number or number array (list/tuple). Start temperatures. Can be one number or an array of numbers.\n\n* `max_function_evals` : int, optional. Maximum number of function evaluations. The default is 1000.\n* `max_iterations_without_progress` : int, optional. Maximum number of iterations without global progress. The default is 250.\n* `step_for_reinit_temperature` : int, optional. After this number of iterations without progress temperatures will be initialized as like start. The default is 90.\n* `reinit_from_best` : boolean, optional. Start algorithm from best solution after reinit temperatures (or from last current solution). The default is False.\n* `seed` : int/None, optional. Random seed (if needed)\n\n## Temperature regimes\n\n### Pattern\n\nThe important part of algorithm is **cooling function**. This function controls temperature value depended on current iteration number, current temperature and start temperature. U can create your own cooling function using pattern:\n\n```python\ndef func(T_last, T0, k):\n    # some code\n    return T_new\n```\n\nHere `T_last` (int/float) is the temperature value from previous iteration, `T0` (int/float) is the start temperature and `k` (int \u003e 0) is the number of iteration. U should use some of this information to create new temperature `T_new`. \n\nIt's highly recommended to build your function to create only positive temperature. \n\n### Available functions\n\nIn `Cooling` class there are several cooling functions:\n* `Cooling.linear(mu, Tmin = 0.01)`\n* `Cooling.exponential(alpha = 0.9)`\n* `Cooling.reverse(beta = 0.0005)`\n* `Cooling.logarithmic(c, d = 1)` - not recommended\n* `Cooling.linear_reverse()`\n\n### Plot temperature\n\nU can see the behavior of cooling function using `SimulatedAnnealing.plot_temperature` method. Let's see several examples: \n\n```python\nfrom SimplestSimulatedAnnleaning import SimulatedAnnealing, Cooling\n\n\n# simplest way to set cooling regime\ntemperature = 100\ncooling = Cooling.reverse(beta = 0.001)\n# we can temperature behaviour using this code\nSimulatedAnnealing.plot_temperature(cooling, temperature, iterations = 100, save_as = 'reverse.png')\n```\n![](tests/reverse.png)\n\n\n```python\n# we can set several temparatures (for each dimention)\ntemperature = [150, 100, 50]\nSimulatedAnnealing.plot_temperature(cooling, temperature, iterations = 100, save_as = 'reverse_diff_temp.png')\n```\n![](tests/reverse_diff_temp.png)\n\n\n```python\n# or several coolings (for each dimention)\ntemperature = 100\ncooling = [\n    Cooling.reverse(beta = 0.0001),\n    Cooling.reverse(beta = 0.0005),\n    Cooling.reverse(beta = 0.001)\n    ]\nSimulatedAnnealing.plot_temperature(cooling, temperature, iterations = 100, save_as = 'reverse_diff_beta.png')\n```\n![](tests/reverse_diff_beta.png)\n\n\n```python\n# all supported coolling regimes\n\ntemperature = 100\ncooling = [\n    Cooling.linear(mu = 1),\n    Cooling.reverse(beta = 0.0007),\n    Cooling.exponential(alpha = 0.85),\n    Cooling.linear_reverse(),\n    Cooling.logarithmic(c = 100, d = 1)\n    ]\nSimulatedAnnealing.plot_temperature(cooling, temperature, iterations = 100, save_as = 'diff_temp.png')\n```\n![](tests/diff_temp.png)\n\n\n```python\n# and we can set own temperature and cooling for each dimention!\n\ntemperature = [100, 125, 150]\ncooling = [\n    Cooling.exponential(alpha = 0.85),\n    Cooling.exponential(alpha = 0.9),\n    Cooling.exponential(alpha = 0.95),\n    ]\nSimulatedAnnealing.plot_temperature(cooling, temperature, iterations = 100, save_as = 'diff_temp_and_cool.png')\n```\n![](tests/diff_temp_and_cool.png)\n\n\n### Difference between coolings\n\nWhy there are so many cooling regimes? For certain task one of them can be such better! In [this script](tests/regimes_temp.py) we can test different cooling for Rastring function:\n\n![](tests/regimes_temp.png)\n\n\n### Multiple coolings\n\nIt's amazing feature to **use different coolings and start temperatures for each dimension**:\n\n```python\nimport math\nimport numpy as np\n\nfrom SimplestSimulatedAnnleaning import SimulatedAnnealing, Cooling, simple_continual_mutation\n\n\ndef Rastrigin(arr):\n    return 10*arr.size+np.sum(arr**2) - 10*np.sum(np.cos(2*math.pi*arr))\n\ndim = 5\n\n\n\nmodel = SimulatedAnnealing(Rastrigin, dim)\n\nbest_solution, best_val = model.run(\n    start_solution = np.random.uniform(-5, 5, dim),\n    mutation = simple_continual_mutation(std = 1),\n    cooling = [ # different cooling for each dimention\n        Cooling.exponential(0.8),\n        Cooling.exponential(0.9),\n        Cooling.reverse(beta = 0.0005),\n        Cooling.linear_reverse(),\n        Cooling.reverse(beta = 0.001)\n        ], \n    start_temperature = 100, \n    max_function_evals = 1000, \n    max_iterations_without_progress = 250, \n    step_for_reinit_temperature = 90,\n    reinit_from_best = False\n    )\n\nprint(best_val)\n\nmodel.plot_report(save_as = 'different_coolings.png')\n```\n![](tests/different_coolings.png)\n\n### Give a change to multiple coolings\n\nMain reason to use multiple coolings is the specifying behavior of each dimension. For example, first dimension of space can be much wider than second dimension therefore it's better to use wider search for first dimension; u can produce it using special `mut` function, using different `start temperatures` and using different `coolings`.\n\nAnother reason to use multiple coolings is the way of selection: for multiple coolings *selection between good and bad solutions applies by each dimension*. So, it increases chances to find better solution. \n\n## About mutation\n\nMutation function is the most important parameter. It determines the behavior of creating new objects using information about current object and about temperature.\nI recommend to count these principles when creating `mut` function:\n\n1. mutant solution should be random but \"close\" to current solution\n2. mutant solution usually should be closer as the temperature decreases \n\nLet's recall the structure of `mut`:\n\n```python\ndef mut(x_as_array, temperature_as_array_or_one_number):\n    # some code\n    return new_x_as_array\n```\nHere `x_as_array` is the current solution and `new_x_as_array` is the mutated solution (random and with same dim, as u remember). Also u should remember that `temperature_as_array_or_one_number` is *number* only for non-multicooling solution. Otherwise (when using several start temperatures of several coolings or both) it is *numpy array*. [See examples](#examples)\n\n\n\n# Examples\n\n## Select best subset\n\nIn this example I show how to select `k` objects from set with `n` objects which will minimize some function (in this example: absolute value of median):\n\n```python\nimport numpy as np\nfrom SimplestSimulatedAnnleaning import SimulatedAnnealing, Cooling\n\nSEED = 3\n\nnp.random.seed(SEED)\n\nSet = np.random.uniform(low = -15, high=5, size = 100) # all set\n\ndim = 10 # how many objects should we choose\n\nindexes = np.arange(Set.size)\n# minimized function -- subset with best |median|\ndef min_func(arr):\n    return abs(np.median(Set[indexes[arr.astype(bool)]]))\n\n# zero vectors with 'dim' ones at random positions \nstart_solution = np.zeros(Set.size)\nstart_solution[np.random.choice(indexes, dim, replace=False)] = 1\n\n# mutation function\n# temperature is the number cuz we will use only 1 cooling, but it's not necessary to use it)\ndef mut(x_as_array, temperature_as_array_or_one_number):\n    mask_one = x_as_array == 1\n    mask_zero = np.logical_not(mask_one)\n\n    new_x_as_array = x_as_array.copy()\n    # replace some zeros with ones\n    new_x_as_array[np.random.choice(indexes[mask_one], 1, replace=False)] = 0\n    new_x_as_array[np.random.choice(indexes[mask_zero], 1, replace=False)] = 1\n\n    return new_x_as_array\n\n# creating a model\nmodel = SimulatedAnnealing(min_func, dim)\n\n# run search\nbest_solution, best_val = model.run(\n    start_solution = start_solution,\n    mutation = mut,\n    cooling = Cooling.exponential(0.9), \n    start_temperature = 100, \n    max_function_evals = 1000, \n    max_iterations_without_progress = 100, \n    step_for_reinit_temperature = 80,\n    seed = SEED\n    )\n\nmodel.plot_report(save_as = 'best_subset.png')\n```\n![](tests/best_subset.png)\n\n## Do best split\n\nLet's take a look at this task:\n```\nsplit set of values {v1, v2, v3, ..., vn} to sets 0, 1, 2, 3\nwith their sizes (volumes determined by user) to complete best sets metric\n```\nOne of ways to solve it:\n```python\nfrom collections import defaultdict\nimport numpy as np\n\nfrom SimplestSimulatedAnnleaning import SimulatedAnnealing, Cooling\n\n################### useful methods\n\ndef counts_to_vec(dic_count):\n    \"\"\"\n    converts dictionary like {1: 3, 2: 4}\n    to array [1, 1, 1, 2, 2, 2, 2]\n    \"\"\"\n    arrs = [np.full(val, fill_value=key) for key, val in dic_count.items()]\n\n    return np.concatenate(tuple(arrs))\n\ndef vec_to_indexes_dict(vector):\n    \"\"\"\n    converts vector like [1, 0, 1, 2, 2]\n    to dictionary with indexes {1: [0, 2], 2: [3, 4]}\n    \"\"\"\n\n    res = defaultdict(list)\n\n    for i, v in enumerate(vector):\n        res[v].append(i)\n    \n    return {int(key): np.array(val) for key, val in res.items() if key != 0}\n\n\n#################### START PARAMS\n\nSEED = 3\n\nnp.random.seed(SEED)\n\nSet = np.random.uniform(low = -15, high=5, size = 100) # all set\nSet_indexes = np.arange(Set.size)\n\n# how many objects should be in each set\ndim_dict = {\n    1: 10,\n    2: 10,\n    3: 7,\n    4: 14\n}\n\n\n# minimized function: sum of means vy each split set\ndef min_func(arr):\n\n    indexes_dict = vec_to_indexes_dict(arr)\n\n    means = [np.mean(Set[val]) for val in indexes_dict.values()]\n\n    return sum(means)\n\n# zero vector with available set labels at random positions \nstart_solution = np.zeros(Set.size, dtype = np.int8)\nlabels_vec = counts_to_vec(dim_dict)\nstart_solution[np.random.choice(Set_indexes, labels_vec.size, replace=False)] = labels_vec\n\n\ndef choice(count = 3):\n    return np.random.choice(Set_indexes, count, replace=False)\n\n# mutation function\n# temperature is the number cuz we will use only 1 cooling, but it's not necessary to use it)\ndef mut(x_as_array, temperature_as_array_or_one_number):\n\n    new_x_as_array = x_as_array.copy()\n    # replace some values\n\n    while True:\n        inds = choice()\n\n        if np.unique(new_x_as_array[inds]).size == 1: # there is no sense to replace same values\n            continue\n        new_x_as_array[inds] = new_x_as_array[np.random.permutation(inds)]\n\n        return new_x_as_array\n\n# creating a model\nmodel = SimulatedAnnealing(min_func, Set_indexes.size)\n\n# run search\nbest_solution, best_val = model.run(\n    start_solution = start_solution,\n    mutation = mut,\n    cooling = Cooling.exponential(0.9), \n    start_temperature = 100, \n    max_function_evals = 1000, \n    max_iterations_without_progress = 100, \n    step_for_reinit_temperature = 80,\n    seed = SEED,\n    reinit_from_best=True\n    )\n\nmodel.plot_report(save_as = 'best_split.png')\n```\n\n![](tests/best_split.png)\n\n\n\n## Travelling salesman problem\n\nLet's try to solve [Travelling salesman problem](https://en.wikipedia.org/wiki/Travelling_salesman_problem) for [berlin52](http://elib.zib.de/pub/mp-testdata/tsp/tsplib/tsp/berlin52.tsp) task. In this task there are 52 cities with coordinates [from file](tests/berlin52_coords.txt).\n\nFirstly, let's import packages:\n\n```python\nimport math\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\nfrom SimplestSimulatedAnnleaning import SimulatedAnnealing, Cooling\n```\n\nSet seed for reproducing:\n\n```python\nSEED = 1\nnp.random.seed(SEED)\n```\n\nRead coordinates and create distance matrix:\n```python\n# read coordinates\ncoords = pd.read_csv('berlin52_coords.txt', sep=' ', header= None, names = ['index', 'x', 'y'])\n\n# dim is equal to count of cities\ndim = coords.shape[0]\n\n# distance matrix\ndistances = np.empty((dim, dim))\n\nfor i in range(dim):\n    distances[i,i] = 0\n    for j in range(i+1, dim):\n        d = math.sqrt(np.sum((coords.iloc[i, 1:]-coords.iloc[j, 1:])**2))\n        distances[i,j] = d\n        distances[j,i] = d\n```\n\nCreate random start solution:\n```python\nindexes = np.arange(dim)\n# some start solution (indexes shuffle)\nstart_solution = np.random.choice(indexes, dim, replace = False)\n```\n\nDefine a function which computes the length of way:\n```python\n# minized function\ndef way_length(arr):\n    s = 0\n    for i in range(1, dim):\n        s += distances[arr[i-1], arr[i]]\n    # also we should end the way in the beggining\n    s += distances[arr[-1], arr[1]]\n\n    return s\n```\n\nLet's visualize start solution:\n```python\n\n\ndef plotData(indices, title, save_as = None):\n    \n    # create a list of the corresponding city locations:\n    locs = [coords.iloc[i, 1:] for i in indices]\n    locs.append(coords.iloc[indices[0], 1:])\n\n    # plot a line between each pair of consequtive cities:\n    plt.plot(*zip(*locs), linestyle='-', color='blue')\n    \n    # plot the dots representing the cities:\n    plt.scatter(coords.iloc[:, 1], coords.iloc[:, 2], marker='o', s = 40, color='red')    \n    plt.title(title)\n    \n    if not (save_as is None):  plt.savefig(save_as, dpi = 300)\n\n    plt.show()\n\n\n# let's plot start solution\nplotData(start_solution, f'start random solution (score = {round(way_length(start_solution), 2)})','salesman_start.png')\n```\n![](tests/salesman_start.png)\n\nIt's really not good solution. I wanna create this mutation function for this task:\n```python\ndef mut(x_as_array, temperature_as_array_or_one_number):\n    # random indexes\n    rand_inds = np.random.choice(indexes, 3, replace = False)\n    # shuffled indexes\n    goes_to = np.random.permutation(rand_inds)\n\n    # just replace some positions in the array\n    new_x_as_array = x_as_array.copy()\n    new_x_as_array[rand_inds] = new_x_as_array[goes_to]\n\n    return new_x_as_array\n```\n\nStart searching:\n```python\n# creating a model\nmodel = SimulatedAnnealing(way_length, dim)\n\n# run search\nbest_solution, best_val = model.run(\n    start_solution = start_solution,\n    mutation = mut,\n    cooling = Cooling.exponential(0.9), \n    start_temperature = 100, \n    max_function_evals = 15000, \n    max_iterations_without_progress = 2000, \n    step_for_reinit_temperature = 80,\n    reinit_from_best = True,\n    seed = SEED\n    )\n\nmodel.plot_report(save_as = 'best_salesman.png')\n```\n![](tests/best_salesman.png)\n\nAnd see our so much better solution:\n```python\nplotData(best_solution, f'result solution (score = {round(best_val, 2)})','salesman_result.png')\n```\n![](tests/salesman_result.png)\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpasaopasen%2Fsimplestsimulatedannealing","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpasaopasen%2Fsimplestsimulatedannealing","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpasaopasen%2Fsimplestsimulatedannealing/lists"}