{ "cells": [ { "cell_type": "markdown", "id": "cd6d9176", "metadata": {}, "source": [ "# SMA Implementation\n", "\n", "This notebook implements the slime mould algorithm as described in [Slime Mould Algorithm](https://doi.org/10.1016/j.future.2020.03.055). This implementation references [the author's MatLab code](\n", "https://github.com/aliasgharheidaricom/Slime-Mould-Algorithm-A-New-Method-for-Stochastic-Optimization-) to resolve ambiguities.\n", "\n", "This notebook uses the MIT license. Parts of the notebook transcribe (from MatLab to Python) work by Heidari under [the MIT license](https://github.com/aliasgharheidaricom/Slime-Mould-Algorithm-A-New-Method-for-Stochastic-Optimization-/blob/master/LICENSE). Heidari\n", "is also an author of the base paper.\n" ] }, { "cell_type": "code", "execution_count": 11, "id": "a37eebd1", "metadata": {}, "outputs": [], "source": [ "from typing import Literal as L, Callable\n", "import numpy as np\n", "import operator\n", "import random\n", "from numpy.random import default_rng" ] }, { "cell_type": "markdown", "id": "d3fa0a68", "metadata": {}, "source": [ "Set random seeds. Note that these seeds are also\n", "used in later cells, for example by `default_rng`." ] }, { "cell_type": "code", "execution_count": 12, "id": "333ea115", "metadata": {}, "outputs": [], "source": [ "GLOBAL_SEED: int = 24601\n", "np.random.seed(GLOBAL_SEED)\n", "random.seed(GLOBAL_SEED)" ] }, { "cell_type": "markdown", "id": "70aa35fa", "metadata": {}, "source": [ "Declare dimension of the problem: the size `D` of\n", "each solution and the number `N` of solutions in the\n", "population. Also declare related type aliases." ] }, { "cell_type": "code", "execution_count": 13, "id": "079bbe4e", "metadata": {}, "outputs": [], "source": [ "#: Number of solutions in the population.\n", "N: int\n", "#: Size of each solution; or dimension of the problem.\n", "D: int\n", "\n", "type Location = np.ndarray[tuple[L['D']],\n", " np.dtype[np.float64]]\n", "\"\"\"Type of a solution in R^D. Location of \"one\"\n", "slime mould in a D-dimensional space.\n", "\n", "Alias for D-vectors.\n", "\"\"\"\n", "\n", "type Population = np.ndarray[tuple[L['N'], L['D']],\n", " np.dtype[np.float64]]\n", "\"\"\"Type of the population in R^{N\\\\times D}. Each ith\n", "item is a Location.\n", "\"\"\"\n", "\n", "#: Fitness of one solution. Semantic alias for Float64\n", "type Fitness = np.float64\n", "\n", "type Fitnesses = np.ndarray[tuple[L['N']],\n", " np.dtype[np.float64]]\n", "\"\"\"Type of the vector of fitnesses. Contains one item\n", "for each of the N solutions.\n", "Alias for D-vectors.\n", "\"\"\"\n", "\n", "\"\"\"A hyperparameter for the algorithm. The canonical code\n", "uses this value.\n", "\"\"\"\n", "\n", "MAX_ITERATION: int\n" ] }, { "cell_type": "markdown", "id": "8f123ed4", "metadata": {}, "source": [ "Define the population initialiser.\n", "Parameters `UB` and `LB` limit the search space." ] }, { "cell_type": "code", "execution_count": 14, "id": "7affb140", "metadata": {}, "outputs": [], "source": [ "def initialise_population(N: int,\n", " dim: int,\n", " UB: Location,\n", " LB: Location)\\\n", " -> Population:\n", " \"\"\"Given parameters, initialise a Population from\n", " parameters.\n", " \"\"\"\n", " \n", " if np.any(UB SMAReport:\n", " \n", "\n", " historical_solutions: list[Population] = []\n", " historical_fitnesses: list[Fitnesses] = []\n", " gen_best_solutions: list[Location] = []\n", " gen_best_fitnesses: list[Fitness] = []\n", "\n", " Xs = initialise_population(N, D, pop_ub, pop_lb)\n", "\n", " #: The best solution.\n", " best_solution: Location\n", " #: Fitness of the best solution.\n", " best_fitness: Fitness\n", "\n", " # Initialise :var:`best_fitness` as the worst\n", " # possible fitness. If algorithm is maximising,\n", " # then best_fitness starts small. Otherwise,\n", " # best_fitness starts large.\n", " if is_maximising:\n", " best_fitness = np.float64(np.finfo(np.float64).min)\n", " else:\n", " best_fitness = np.float64(np.finfo(np.float64).max)\n", "\n", " #: N-vector. S[i] is the fitness of the i^th solution.\n", " S: Fitnesses\n", "\n", " for t in range(max_iteration):\n", " # Bound each Xs[i][j] to between UB[j] and LB[j]\n", "\n", " for i in range(len(Xs)):\n", " # Xs[i,:] is the ith slice\n", " _ub_violation_mask = Xs[i,:] > ub\n", "\n", " Xs[i,:] = np.multiply(Xs[i,:], ~_ub_violation_mask) +\\\n", " np.multiply(ub, _ub_violation_mask)\n", "\n", " _lb_violation_mask = Xs[i,:] < lb\n", " Xs[i,:] = np.multiply(Xs[i,:], ~_lb_violation_mask) +\\\n", " np.multiply(lb, _lb_violation_mask)\n", "\n", " # Calculate fitnesses. S[i] is fitness(X[i,:])\n", " S = np.apply_along_axis(objective, 1, Xs)\n", "\n", " # Algorithm 1: Calculate the W by Eq. (2.5).\n", " # small_dict is an ordered list. It maps\n", " # each index `smell_index: int` to the\n", " # smell_index^{nd} best solution.\n", " # smell_indices: (dim,) of int\n", " # smells: (dim,) of floats\n", "\n", " # Implemented according to the canonical implementation.\n", " # The best is at index 0; the worst is at index N.\n", " # As such, sort according to :var:`IS_MAXIMISING`:\n", " smell_dict = np.array(sorted(list(enumerate(S)), # type: ignore\n", " key=lambda x: x[1],\n", " reverse=True if is_maximising\\\n", " else False))\n", "\n", " weights = np.zeros(shape=(N, D))\n", " bF: np.float64 = smell_dict[0][1]\n", " wF: np.float64 = smell_dict[-1][1]\n", "\n", " # Partition smell_dict into two roughly-equal-sized\n", " # matrices containing the better / worse halves.\n", " smell_dict_condition = smell_dict[0:int(N/2)]\n", " smell_dict_others = smell_dict[int(N/2):-1]\n", "\n", " # This variable is named S in the canonical implementation.\n", " # However, in the paper S means something different.\n", " # This is name is to avoid ambiguity.\n", " # `eps` is to avoid division by zero if bF=wF.\n", " # The number is so infinitesimally small that we\n", " # could probably ignore the probability that\n", " # bF and wF differ by exactly eps.\n", " S_eps = best_fitness - wF + np.finfo(np.float64).eps # was bF in paper ... maybe try to be more aggressive?\n", "\n", " for j in range(0, D):\n", " for smell_index, smell_order in smell_dict_condition:\n", " weights[int(smell_index), j] = 1 + random.random()*np.log10((bF-smell_order)/(S_eps)+1)\n", "\n", " for smell_index, smell_order in smell_dict_others:\n", " weights[int(smell_index), j] = 1 - random.random()*np.log10((bF-smell_order)/(S_eps)+1)\n", "\n", " if is_maximising:\n", " is_better_than = operator.gt\n", " else:\n", " is_better_than = operator.lt\n", "\n", " if is_better_than(bF, best_fitness):\n", " # Recall that the 0th solution is best.\n", " best_fitness = smell_dict[0][1]\n", " best_solution = Xs[int(smell_dict[0][0])]\n", " \n", " # Replacing t in original with t+1 here. This is because\n", " # Matlab indices begin as 0.\n", " a = np.arctanh(1-(t+1)/max_iteration) # Code says it's (2.4)\n", " b = 1 - (t+1)/max_iteration\n", "\n", "\n", " historical_solutions.append(Xs.copy())\n", " historical_fitnesses.append(S.copy())\n", " gen_best_solutions.append(Xs[int(smell_dict[0][0])].copy())\n", " gen_best_fitnesses.append(smell_dict[0][1].copy())\n", "\n", " for i in range(N):\n", " rand=random.random()\n", " # The code ... uses rand directly?\n", " if rand < Z: # Code says it's (2.7)\n", " Xs[i,:] = (pop_ub-pop_lb)*random.random() + pop_lb\n", " else:\n", " # AllFitness in code is S here\n", " \n", " p = np.tanh(np.abs(S[i] - best_fitness))\n", " vb = np.random.uniform(low=-a, high=a, size=(D,)) # Code says it's 2.3\n", " vc = np.random.uniform(low=-b, high=b, size=(D,))\n", "\n", " for j in range(D):\n", " r = random.random()\n", " A = random.randint(0, N-1)\n", " B = random.randint(0, N-1)\n", " \n", " if r < p:\n", " Xs[i, j] = best_solution[j] + vb[j] * (weights[i, j]*Xs[A,j]-Xs[B,j])\n", " else:\n", " Xs[i, j] = vc[j] * Xs[i,j]\n", "\n", "\n", " result: SMAReport = SMAReport(\n", " solutions=historical_solutions,\n", " fitnesses=historical_fitnesses,\n", " best_solution=best_solution,\n", " best_fitness=best_fitness,\n", " gen_best_solutions=gen_best_solutions,\n", " gen_best_fitnesses=gen_best_fitnesses,\n", " )\n", " return result\n", "\n", " " ] }, { "cell_type": "markdown", "id": "759df814", "metadata": {}, "source": [ "Parameterise and run the algorithm." ] }, { "cell_type": "code", "execution_count": 16, "id": "e3aaac6a", "metadata": {}, "outputs": [], "source": [ "from ograph import ofunc, oplot" ] }, { "cell_type": "code", "execution_count": null, "id": "c05f6a0b", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": 17, "id": "f65740d4", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[-2.47592429 -1.81997011]\n", " [-2.95540877 -3.33771139]\n", " [-2.74391284 -1.82419344]\n", " [-3.70899605 -1.68464243]\n", " [-3.12115934 -1.37014237]\n", " [-2.53291771 -3.64338561]\n", " [-1.72119328 -1.28885633]\n", " [-2.33623507 -1.08980774]\n", " [-3.26921986 -2.55601543]\n", " [-3.12279309 -2.57700669]\n", " [-3.38698749 -1.15188707]\n", " [-2.05444234 -1.46576723]\n", " [-1.60774563 -3.08985395]\n", " [-1.35949565 -1.44753345]\n", " [-2.03237569 -2.18611263]\n", " [-1.91113253 -3.40816876]\n", " [-3.45566262 -3.36016751]\n", " [-3.61281961 -2.52430802]\n", " [-1.02348297 -2.21315978]\n", " [-1.92142152 -2.66819629]\n", " [-2.39064646 -2.91871284]\n", " [-1.23466717 -2.51284576]\n", " [-2.55051423 -3.798853 ]\n", " [-3.95026681 -3.83633743]\n", " [-3.96621018 -1.98419591]\n", " [-3.50945244 -1.27658987]\n", " [-3.22636507 -2.85088508]\n", " [-2.44282669 -2.4260564 ]\n", " [-1.53628828 -2.69364699]\n", " [-2.36810088 -1.00410217]\n", " [-3.11530769 -2.05087961]\n", " [-2.05258559 -3.36806285]\n", " [-3.53886252 -3.9694065 ]\n", " [-1.3621931 -1.16921042]\n", " [-1.696319 -2.74432217]\n", " [-3.81658648 -2.77322841]\n", " [-3.93657671 -1.2421323 ]\n", " [-1.34358889 -3.06222432]\n", " [-3.76172238 -3.62649093]\n", " [-1.38924747 -2.12576489]\n", " [-1.80712843 -2.31343234]\n", " [-3.40268273 -2.27668321]\n", " [-1.16427419 -2.73559948]\n", " [-1.93870581 -1.73864863]\n", " [-2.37392866 -2.75147314]\n", " [-3.87192287 -2.24350892]\n", " [-2.34202431 -1.41521903]\n", " [-1.03185307 -1.42448362]\n", " [-2.27137564 -1.49339086]\n", " [-2.5378265 -3.74476359]\n", " [-2.10174376 -3.41777756]\n", " [-3.18254033 -1.21881549]\n", " [-1.32006398 -3.64269858]\n", " [-1.91151534 -1.08159944]\n", " [-1.47428545 -3.52296 ]\n", " [-2.79978241 -3.83048691]\n", " [-2.73554389 -2.50570139]\n", " [-2.60446389 -1.32918957]\n", " [-2.44779074 -2.661567 ]\n", " [-1.29374689 -2.51676992]\n", " [-1.47920299 -2.18365621]\n", " [-2.16098851 -1.57941823]\n", " [-2.82107172 -2.55913883]\n", " [-3.59611955 -3.69247776]\n", " [-3.28155639 -2.52486625]\n", " [-3.35494193 -1.91654424]\n", " [-3.16305946 -1.7731639 ]\n", " [-2.22308459 -1.69209623]\n", " [-1.09012889 -1.71263931]\n", " [-3.88693587 -3.09800484]\n", " [-3.3473536 -2.84332726]\n", " [-3.73451444 -3.01135429]\n", " [-3.76125346 -2.45202997]\n", " [-1.35278209 -3.56194624]\n", " [-1.63793391 -3.17910374]\n", " [-2.9649655 -2.84549329]\n", " [-1.11609482 -3.01301184]\n", " [-1.2262371 -2.9566359 ]\n", " [-2.28486943 -2.06914252]\n", " [-3.7267008 -1.32296343]\n", " [-2.56948376 -1.09182448]\n", " [-3.98085922 -1.74046479]\n", " [-2.91341855 -1.56751214]\n", " [-1.33330165 -2.74246792]\n", " [-3.26042757 -3.32370859]\n", " [-3.47616085 -3.90004547]\n", " [-2.08280041 -2.98357382]\n", " [-2.47823356 -2.89552476]\n", " [-1.7924004 -2.40325816]\n", " [-3.31049065 -3.8383048 ]\n", " [-1.53415311 -1.37880931]\n", " [-3.52002152 -2.45316001]\n", " [-3.93736291 -3.62328251]\n", " [-1.24878303 -1.17312098]\n", " [-3.62471116 -1.49570033]\n", " [-2.80068436 -1.13425278]\n", " [-3.2638772 -2.18697822]\n", " [-3.45478549 -3.18581588]\n", " [-3.62457202 -3.98949789]\n", " [-3.68974153 -2.13974449]\n", " [-3.24142622 -1.37405943]\n", " [-2.66465865 -2.32745027]\n", " [-3.64719944 -3.91604168]\n", " [-1.72132094 -3.48397851]\n", " [-2.27790202 -1.19762802]\n", " [-1.95870719 -2.47135787]\n", " [-2.99962105 -2.56463065]\n", " [-3.34058563 -2.95747866]\n", " [-1.07865836 -2.86619631]\n", " [-2.86550952 -3.51947678]\n", " [-3.15997956 -1.31631882]\n", " [-3.81358945 -3.47630911]\n", " [-1.80326271 -2.84731933]\n", " [-3.81049119 -3.3414486 ]\n", " [-2.88653917 -2.26224947]\n", " [-3.75765269 -2.03059638]\n", " [-1.69842732 -1.67950422]\n", " [-1.10621879 -1.91947618]\n", " [-2.59497986 -1.20477833]\n", " [-2.53341649 -2.10843739]\n", " [-2.39596934 -1.01293467]\n", " [-2.05177285 -2.77754899]\n", " [-1.24294954 -3.30699857]\n", " [-3.74531197 -2.48200374]\n", " [-1.81253217 -3.13723949]\n", " [-2.93803093 -1.93859849]\n", " [-3.24906568 -2.76776741]\n", " [-2.18681072 -2.05932403]\n", " [-1.83690268 -1.54028669]\n", " [-2.31175863 -1.71190059]\n", " [-1.18953702 -1.93295709]\n", " [-2.21999599 -1.7823127 ]\n", " [-2.38500489 -1.82205898]\n", " [-1.4252258 -2.58258284]\n", " [-2.42025759 -3.91852256]\n", " [-3.23610232 -2.62886081]\n", " [-3.62572128 -2.27520051]\n", " [-2.32607002 -1.25987613]\n", " [-1.46239442 -2.77781081]\n", " [-1.025142 -3.56547457]\n", " [-1.26883583 -2.30054105]\n", " [-1.23096913 -3.72684842]\n", " [-2.77318501 -2.79151009]\n", " [-2.33445358 -3.19602229]\n", " [-2.96505354 -2.30971645]\n", " [-2.41090855 -2.54689573]\n", " [-1.32452311 -1.11226622]\n", " [-2.53921083 -1.25020504]\n", " [-2.36262288 -3.2611987 ]\n", " [-1.69692747 -1.2936879 ]\n", " [-3.43556928 -1.87757262]\n", " [-3.95422027 -2.40760427]\n", " [-1.10131194 -1.90829358]\n", " [-1.80428306 -3.2513341 ]\n", " [-3.44366843 -2.2996864 ]\n", " [-3.62591078 -3.70612528]\n", " [-2.74077055 -2.77437767]\n", " [-3.66911411 -3.26080062]\n", " [-3.72013603 -3.3772269 ]\n", " [-3.81412233 -2.94955127]\n", " [-2.3324628 -1.47515259]\n", " [-2.22100761 -2.58766684]\n", " [-2.07797463 -3.51482905]\n", " [-1.47731733 -3.6234165 ]\n", " [-3.47019464 -3.63316133]\n", " [-2.90143483 -1.99771576]\n", " [-1.75228291 -1.39501959]\n", " [-3.8114033 -3.68825511]\n", " [-1.20786874 -3.96460392]\n", " [-2.83593277 -2.3252146 ]\n", " [-2.62628278 -1.50187703]\n", " [-3.29921907 -1.23407264]\n", " [-2.32970278 -3.15306616]\n", " [-2.30941998 -1.75723721]\n", " [-3.1245127 -3.63280641]\n", " [-1.05138074 -1.96040215]\n", " [-2.36792012 -3.23299844]\n", " [-1.42315267 -2.35999401]\n", " [-3.47842811 -1.36905547]\n", " [-3.11673082 -2.93499453]\n", " [-3.63518322 -3.97754817]\n", " [-3.3953347 -1.47998179]\n", " [-1.15114295 -3.86829738]\n", " [-2.28704496 -1.44676427]\n", " [-1.16091465 -1.79337725]\n", " [-1.85292888 -1.15332267]\n", " [-2.76692174 -2.68306539]\n", " [-2.12602633 -2.85424294]\n", " [-1.34284003 -3.05060179]\n", " [-1.78918981 -1.7868207 ]\n", " [-1.87361448 -1.09093413]\n", " [-3.10341507 -3.53840054]\n", " [-1.99017561 -2.84100373]\n", " [-2.83066363 -2.27584255]\n", " [-2.12206382 -1.15996083]\n", " [-2.66494694 -1.04418838]\n", " [-1.42596273 -1.30656911]\n", " [-1.45538376 -1.81875416]\n", " [-2.7928368 -1.27597375]\n", " [-3.14304053 -2.39254982]]\n" ] } ], "source": [ "N = 200\n", "D = 2\n", "POP_UB = np.ones(shape=(D,)) * -1\n", "POP_LB = np.ones(shape=(D,)) * -4\n", "UB = np.ones(shape=(D,)) * 7\n", "LB = np.ones(shape=(D,)) * -7\n", "IS_MAXIMISING: bool = True\n", "Z = 0.03\n", "MAX_ITERATION = 200\n", "\n", "\n", "#: The objective function\n", "OBJECTIVE: Callable[[Location], Fitness] =\\\n", " lambda x: ofunc.himmelblau(x[0],\n", " x[1]) * -1\\\n", " if IS_MAXIMISING else 1 # type: ignore\n", "\n", "\n", "report: SMAReport = sma(objective = OBJECTIVE,\n", " is_maximising = IS_MAXIMISING,\n", " max_iteration = MAX_ITERATION,\n", " pop_ub=POP_UB,\n", " pop_lb=POP_LB,\n", " ub=UB,\n", " lb=LB,\n", " N=N,\n", " D=D,\n", " Z=Z)" ] }, { "cell_type": "markdown", "id": "eab0589e", "metadata": {}, "source": [ "## Visualise" ] }, { "cell_type": "code", "execution_count": 18, "id": "dccbe381", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Best solution is [-3.31078708 -3.3121009 ], with fitness -0.032185163502593615\n" ] } ], "source": [ "print(f\"Best solution is {report.best_solution}, with fitness {report.best_fitness}\")" ] }, { "cell_type": "code", "execution_count": null, "id": "6b6915e1", "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from ograph.swarm import plot_fitnesses\n", "\n", "plot_fitnesses(report.gen_best_fitnesses,\n", " best_selector=max)" ] }, { "cell_type": "code", "execution_count": 20, "id": "f81646ca", "metadata": {}, "outputs": [ { "ename": "NameError", "evalue": "name 'plot_bests' is not defined", "output_type": "error", "traceback": [ "\u001b[31m---------------------------------------------------------------------------\u001b[39m", "\u001b[31mNameError\u001b[39m Traceback (most recent call last)", "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[20]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43mplot_bests\u001b[49m(report.solutions, report.gen_best_fitnesses)\n", "\u001b[31mNameError\u001b[39m: name 'plot_bests' is not defined" ] } ], "source": [ "plot_bests(report.solutions, report.gen_best_fitnesses)" ] }, { "cell_type": "markdown", "id": "ab7c7c25", "metadata": {}, "source": [ "### Visualise Particles\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "id": "059af5f6", "metadata": {}, "outputs": [], "source": [ "from ograph.swarm import plot_positions, animate_positions" ] }, { "cell_type": "code", "execution_count": null, "id": "d066ce85", "metadata": {}, "outputs": [], "source": [ "plot_positions(report.solutions,\n", " objective=lambda x, y: OBJECTIVE((x, y)))" ] }, { "cell_type": "markdown", "id": "f77fde49", "metadata": {}, "source": [ "Because the ipython notebook might not correctly render gifs, the animation will be saved to the working directory." ] }, { "cell_type": "code", "execution_count": null, "id": "5d91214e", "metadata": {}, "outputs": [], "source": [ "animate_positions(report.solutions,\n", " objective=lambda x, y: OBJECTIVE((x, y)))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.10" } }, "nbformat": 4, "nbformat_minor": 5 }