{"id":15442919,"url":"https://github.com/salvatorebarone/pyamosa","last_synced_at":"2025-04-19T20:13:37.963Z","repository":{"id":41327563,"uuid":"436768641","full_name":"SalvatoreBarone/pyAMOSA","owner":"SalvatoreBarone","description":"Python implementation of the Archived Multi-Objective Simulated Annealing (AMOSA) optimization heuristic","archived":false,"fork":false,"pushed_at":"2024-02-29T13:10:18.000Z","size":37105,"stargazers_count":10,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-04-22T10:13:24.980Z","etag":null,"topics":["multi-objective-optimization","optimization-algorithms","python","python-3","python3","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":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SalvatoreBarone.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-12-09T21:38:27.000Z","updated_at":"2024-04-18T07:31:35.000Z","dependencies_parsed_at":"2024-10-01T19:31:46.276Z","dependency_job_id":"2cc6a2d1-17d1-46bd-8576-d6d201abd39b","html_url":"https://github.com/SalvatoreBarone/pyAMOSA","commit_stats":{"total_commits":97,"total_committers":2,"mean_commits":48.5,"dds":"0.12371134020618557","last_synced_commit":"729c665bf02d13442ff2fc74deba863007679601"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SalvatoreBarone%2FpyAMOSA","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SalvatoreBarone%2FpyAMOSA/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SalvatoreBarone%2FpyAMOSA/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SalvatoreBarone%2FpyAMOSA/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SalvatoreBarone","download_url":"https://codeload.github.com/SalvatoreBarone/pyAMOSA/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241587784,"owners_count":19986628,"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":["multi-objective-optimization","optimization-algorithms","python","python-3","python3","simulated-annealing"],"created_at":"2024-10-01T19:31:39.057Z","updated_at":"2025-03-03T00:32:16.588Z","avatar_url":"https://github.com/SalvatoreBarone.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pyAMOSA \npyAMOSA is a python implementation of the Archived Multi-Objective Simulated Annealing optimization heuristic [1].\n\n## Installing the module\nYou can install this module using ```pip```.\n\n```bash\npip install pyamosa\n```\n\nIf you want to install it manually, you can do it by simply running the provided ```setup.py``` script as follows:\n```bash\n$ python3 setup.py install\n```\n\n# Defining and solving a problem\n\nIn pyAMOSA a problem is defined by an object that contains some metadata, for instance the number of decision variables, \ntheir data type, the number of objectives, the number of constraints, lower and upper bounds for decision variables.\nThese attributes are supposed to be defined in the constructor. \n\nObjects defining problems can be defined by inheriting from ```AMOSA.Problem```, and thus overriding the ```__init__``` \nmethod to define the above-mentioned attributes. \n\nThe actual objective-functions evaluation takes place in the ```evaluate ``` method, which aims to fill the ```out``` \ndictionary with approriate data. The objective-function values are supposed to be written into ```out[\"f\"]```, while \nthe constraints into ```out[\"g\"]```, if the ```num_of_constraints``` attribute is greater than zero.\nThe ```evaluate``` will be called for each solution, allowing easy parallelization using processes, and regardless of \nthe number of solutions being asked to be evaluated, it retrieves a vector ```x``` of values for decision variables of\nthe problem.\n\nHow the objective-functions and constraint values are calculated is irrelevant from pyAMOSA's point of view. \nWhether it is a simple mathematical equation or a discrete-event simulation, you only have to ensure that for each input\nthe corresponding values have been set.\n\nSuppose you want to solve the ZDT1 problem [2], i.e. \n\n\u003cimg src=\"https://render.githubusercontent.com/render/math?math=min\\begin{cases}f_1(x)=x_1\\\\f_2(x)=g(x)\\cdot h(f_1(x),g(x))\\end{cases}\"\u003e\n\nwhere\n\n\u003cimg src=\"https://render.githubusercontent.com/render/math?math=g(x)=1+\\frac{9}{29}\\left(\\sum_{i=2}^n x_i\\right)\"\u003e\n\nand\n\n\u003cimg src=\"https://render.githubusercontent.com/render/math?math=h(f(x),g(x))=1-\\sqrt{\\frac{f(x)}{g(x)}}\"\u003e\n\nwith\n\n\u003cimg src=\"https://render.githubusercontent.com/render/math?math=0\\le x_i\\le1 i=1 ... 30\"\u003e\n\n\n\n```python\nimport pyamosa, numpy as np\n\nclass ZDT1(pyamosa.Problem):\n    n_var = 30\n\n    def __init__(self):\n\n        pyamosa.Problem.__init__(self, ZDT1.n_var, [pyamosa.Type.REAL] * ZDT1.n_var, [0.0]*ZDT1.n_var, [1.0] * ZDT1.n_var, 2, 0)\n\n    def evaluate(self, x, out):\n        f = x[0]\n        g = 1 + 9 * sum(x[1:]) / (self.num_of_variables - 1)\n        h = 1 - np.sqrt(f / g)\n        out[\"f\"] = [f, g * h ]\n```\n\nNow, you have to build a proper problem object and also an optimization-engine, as follows.\n```python\nif __name__ == \"__main__\":\n    problem = ZDT1()\n\n    optimizer = pyamosa.Optimizer(...)\n```\n\nThe ```pyamosa.Optimizer``` class allows setting a vast plethora of configuration parameters governing the behavior of the \nheuristic. You must do it by creating an ```pyamosa.Config``` object, as follows\n\n```python\n    config = pyamosa.Config()\n    config.archive_hard_limit = 100\n    config.archive_soft_limit = 200\n    config.archive_gamma = 2\n    config.clustering_max_iterations = 300\n    config.hill_climbing_iterations = 500\n    config.initial_temperature = 500\n    config.cooling_factor = 0.9\n    config.annealing_iterations = 1000\n    config.annealing_strength = 1\n    config.multiprocess_enabled = True\n```\n\n - the ```archive_hard_limit``` attribute allows setting the HL parameter of the heuristic, i.e., the hard limit on the archive size;\n - the ```archive_soft_limit``` attribute allows setting the SL parameter of the heuristic, i.e., the soft limit on the archive size;\n - the ```hill_climbing_iterations``` is the number of refinement iterations performed during the initial hill-climbing refinement;\n - the ```archive_gamma``` attribute allows governing the amount of initial candidate solutions that are generated duting the archive initialization; \n - the ```clustering_max_iterations``` allows foverning the maximum iterations performed during k-means clustering;\n - the ```annealing_iterations``` allows governing the amount of refinement iterations performed during the main-loop of the heuristic;\n - the ```initial_temperature``` is the initial temperature of the matter;\n - the ```cooling_factor``` governs how quickly the temperature of the matter decreases during the annealing process.\n - the ```annealing_strength``` governs the strength of random perturbations during the annealing phase; specifically, the number of variables whose value is affected by perturbation.\n - the ```multiprocess_enabled``` parameter allows enabling/disabling process-based parallelism.\n \nNow you can proceed solving the problem.\n```\n    optimizer.run(problem, termination_criterion)\n```\n\nKindle note the ```termination_criterion``` paramerer. You can chose one of the following three:\n  1. ```pyamosa.StopMinTemperature```: this is the classic termination criterion for simulated annealing: when the temperature of the matter is lower than the threshold, the algorithm is terminated. For instance, to run until the temperature goes below ```1e-7```, the termination criterion can be defined as follows:\n  ```python\n  pyamosa.StopMinTemperature(1e-7)\n  ```\n  2. ```pyamosa.StopMaxTime```: the termination can also be based on the time of the algorithm to be executed. For instance, to run an algorithm for 3 hours, 30 minutes, the termination can be defined as it follows (***note the initial hill-climbing is taken into account!***):\n```python\ntermination = pyamosa.StopMaxTime(\"3:30\")\n```\n  3. ```pyamosa.StopPhyWindow```: the most interesting stopping criterion is to use objective space change to decide whether to terminate the algorithm. Here, we resort to a simple and efficient procedure to determine whether to stop or not, described in [3]: it is based on the inter-generational distance (IGD), and it allows to stop the algorithm in case it cannot improve the Pareto front in a sequence of iterations. Say you want to stop if the algorithm is unable to improve in 10 iterations (meaning complete algorithm iterations, each of which consists of the number of annealing iterations as defined by the corresponding configuration parameter); then, the termination criterion can be defined as it follows\n  ```python\n  termination = pyamosa.StopPhyWindow(10)\n  ```\n  \n\nAt the end of execution, you can access the Pareto-front and the Pareto-set through the ```pareto_front()``` and \n```pareto_set()``` methods of the ```pyamosa.Optimizer``` class. You can also save the archive on CSV or JSON files, using the \n```archive_to_csv()``` or the ```archive_to_json()``` methods. The class also provides the ```plot_pareto()```, that plots\nthe Pareto-front resulting from the run.\n\n## Constraint handling\n\nConstraint handling is often neglected in frameworks but is indeed an essential aspect of optimization. Indeed, the \nreturned optimum is always required to be feasible. \n\nIn pyAMOSA, inequality constraints are always defined as \n\u003cimg src=\"https://render.githubusercontent.com/render/math?math=c(x)\\le0\"\u003e constraints. Thus, constraint violation is\ndefined as follows: a solution is considered as feasible if all constraint violations are less than zero, while a \nsolution is considered as infeasible if at least one constraint violation is larger than zero.\n\nSuppose you whant to impose \u003cimg src=\"https://render.githubusercontent.com/render/math?math=-x^4\\ge-2\"\u003e. This has to be\nconverted in the less-or-equal form, thus \u003cimg src=\"https://render.githubusercontent.com/render/math?math=-x^4-2\\le0\"\u003e.\n\nAs objective-functions, constraints evaluation also takes place in the ```evaluate ``` method. You can fill the \n```out``` dictionary as follows.\n```\nclass Problem(pyamosa.Optimizer.Problem):\n    ...    \n    def evaluate(self, x, out):\n        ...\n        out[\"g\"] = [ ..., x**4 - 2, ... ]\n```\nFor instance, consider the BNH test problem\n\n*min*: \n\n\u003cimage src=\"https://latex.codecogs.com/svg.image?f_1(\\textbf{x})=4x_1^2+4x_2^2\"/\u003e\n\n\u003cimage src=\"https://latex.codecogs.com/svg.image?f_2(\\textbf{x})=(x_1-5)^2+(x_2-5)^2\"/\u003e\n\n*s.t.*\n\n\u003cimage src=\"https://latex.codecogs.com/svg.image?(x_1-5)^2+x_2^2\\le25\"/\u003e\n\n\n\u003cimage src=\"https://latex.codecogs.com/svg.image?(x_1-8)^2+(x_2+3)^2\\ge7.7\"/\u003e\n\n\u003cimage src=\"https://latex.codecogs.com/svg.image?0 \\le x_1 \\le 5\"/\u003e\n\n\u003cimage src=\"https://latex.codecogs.com/svg.image?0 \\le x_2 \\le 3\"/\u003e\n\n\nIt can be defined as it follows:\n```python\nimport pyamosa, numpy as np\n\n\nclass BNH(pyamosa.Problem):\n    n_var = 2\n\n    def __init__(self):\n        pyamosa.Problem.__init__(self, BNH.n_var, [pyamosa.Type.REAL] * BNH.n_var, [0.0] * BNH.n_var, [5.0, 3.0], 2, 2)\n\n    def evaluate(self, x, out):\n        f1 = 4 * x[0] ** 2 + 4 * x[1] ** 2\n        f2 = (x[0] - 5) ** 2 + (x[1] - 5) ** 2\n        g1 = (x[0] - 5) ** 2 + x[1] ** 2 - 25\n        g2 = 7.7 - (x[0] - 5) ** 2 - (x[1] + 3) ** 2\n        out[\"f\"] = [f1, f2 ]\n        out[\"g\"] = [g1, g2]\n```\n\n## Improving a previous run\n\nWhen calling the ```run()``` method, you can specify the path of a JSON file containing the archive resulting from a\nprevious run. Solutions from this archive will be considered as a starting point for a new run of the heuristic, \npossibly resulting in even better solutions.\n\n# Understanding the log prints\nWhen calling the ```run()``` method, during the annealing procedure, the optimizers will print several statistical information in a table format.\nThese are pretty useful for evaluating the effectiveness of the optimization process -- in specific whether it's going toward either convergence or diversity in the population, etc. -- so it's worth discoursing them.\n\n  - *temp*.: it is the current temperature of the matter; refer to [1] for further details on its impact on the optimization process;\n  - *eval*: it is the number of fitness-function evaluations;\n  - *nds*: it is the number of non-dominated solutions the algorithm found until then;\n  - *feas*: it is the number of **feasible** non-dominated solutions (i.e., those satisfying constraints) the algorithm found until then;\n  - *cv min* and *cv avg*: minimum and average constraint violation, computed on unfeasible non-dominated solutions the algorithm found until then;\n  - *D\\** and *Dnad*: movement of the *ideal* and *nadir* idealized extreme points in the object-space; whether the algotithm is going toward convergence, they tend to be higher (the Pareto front is moving a lot!); see [3] for further details;\n  - *phi*: the *intergenerational distance index*, computed on candidate solutions from the previous annealing iteration *P'* and candidate solutions resulting from the very last annealing iteration *P*; this allows monitoring; if the Pareto front is stationary, and can be improved neither by convergence nor by diversity, this value is close to zero; this metric is taken into consideration to determine the early termination condition; see [3] for further details;\n  - *C(P', P)* and *C(P, P')*: the *coverage index* as defined in [4], computed on candidate solutions from the previous annealing iteration *P'* and candidate solutions resulting from the very last annealing iteration *P* and vice-versa, respectively; in general, *C(A,B)* is percentage the solutions in *B* that are dominated by at least one solution in *A*, where *A* and *B* are two Pareto fronts; therefore, *C(P, P')* should be alway greater than *C(P', P)* through the optimization process.\n\n# References\n1. Bandyopadhyay, S., Saha, S., Maulik, U., \u0026 Deb, K. (2008). A simulated annealing-based multiobjective optimization algorithm: AMOSA. IEEE transactions on evolutionary computation, 12(3), 269-283.\n2. Deb, K. (2001). Multiobjective Optimization Using Evolutionary Algorithms. New York: Wiley, 2001\n3. Blank, Julian, and Kalyanmoy Deb. \"A running performance metric and termination criterion for evaluating evolutionary multi-and many-objective optimization algorithms.\" 2020 IEEE Congress on Evolutionary Computation (CEC). IEEE, 2020.\n4. Zitzler, E., e L. Thiele. \"Multiobjective evolutionary algorithms: a comparative case study and the strength Pareto approach\". IEEE Transactions on Evolutionary Computation 3, fasc. 4 (novembre 1999).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsalvatorebarone%2Fpyamosa","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsalvatorebarone%2Fpyamosa","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsalvatorebarone%2Fpyamosa/lists"}