{"id":19167629,"url":"https://github.com/thomasweise/moptipy","last_synced_at":"2026-03-14T05:18:19.776Z","repository":{"id":37739889,"uuid":"336915869","full_name":"thomasWeise/moptipy","owner":"thomasWeise","description":"Implementation of metaheuristic optimization methods in Python for scientific, industrial, and educational scenarios. Experiments can be executed in parallel or in a distributed fashion. Experimental results can be evaluated in various ways, including diagrams, tables, and export to Excel.","archived":false,"fork":false,"pushed_at":"2025-05-21T11:33:31.000Z","size":7213,"stargazers_count":24,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-05-21T12:31:04.785Z","etag":null,"topics":["evolutionary-algorithms","evolutionary-computation","experimentation","genetic-algorithm","heuristic-optimization","hill-climbing","memetic-algorithms","metaheuristics","optimization","optimization-algorithms","python","random-walk","randomized-local-search","replicability","simulated-annealing"],"latest_commit_sha":null,"homepage":"https://thomasweise.github.io/moptipy/","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/thomasWeise.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-02-07T23:51:37.000Z","updated_at":"2025-05-21T11:18:25.000Z","dependencies_parsed_at":"2023-10-15T09:09:50.471Z","dependency_job_id":"c51d4646-b08c-41db-990a-833c736382bb","html_url":"https://github.com/thomasWeise/moptipy","commit_stats":null,"previous_names":[],"tags_count":161,"template":false,"template_full_name":null,"purl":"pkg:github/thomasWeise/moptipy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thomasWeise%2Fmoptipy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thomasWeise%2Fmoptipy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thomasWeise%2Fmoptipy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thomasWeise%2Fmoptipy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thomasWeise","download_url":"https://codeload.github.com/thomasWeise/moptipy/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thomasWeise%2Fmoptipy/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262035179,"owners_count":23248357,"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":["evolutionary-algorithms","evolutionary-computation","experimentation","genetic-algorithm","heuristic-optimization","hill-climbing","memetic-algorithms","metaheuristics","optimization","optimization-algorithms","python","random-walk","randomized-local-search","replicability","simulated-annealing"],"created_at":"2024-11-09T09:38:39.735Z","updated_at":"2026-02-13T02:13:01.647Z","avatar_url":"https://github.com/thomasWeise.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![make build](https://github.com/thomasWeise/moptipy/actions/workflows/build.yml/badge.svg)](https://github.com/thomasWeise/moptipy/actions/workflows/build.yml)\n[![pypi version](https://img.shields.io/pypi/v/moptipy)](https://pypi.org/project/moptipy)\n[![pypi downloads](https://img.shields.io/pypi/dw/moptipy.svg)](https://pypistats.org/packages/moptipy)\n[![coverage report](https://shields.io/badge/pytest-coverage-009000)](https://thomasweise.github.io/moptipy/tc/index.html)\n[![https://thomasweise.github.io](https://shields.io/badge/home-thomasweise.github.io-009000)](https://thomasweise.github.io)\n\n\n# moptipy: Metaheuristic Optimization in Python\n\n- [Introduction](#1-introduction)\n- [Installation](#2-installation)\n- [How-Tos](#3-how-tos)\n  - [Applying 1 Algorithm Once to 1 Problem](#31-how-to-apply-1-optimization-algorithm-once-to-1-problem-instance)\n  - [Run a Series of Experiments](#32-how-to-run-a-series-of-experiments)\n  - [How to Solve an Optimization Problem](#33-how-to-solve-an-optimization-problem)\n    - [Defining a New Problem](#331-define-a-new-problem-type)\n    - [Defining a New Algorithm](#332-define-a-new-algorithm)\n    - [Applying an Own Algorithm to an Own Problem](#333-applying-an-own-algorithm-to-an-own-problem)\n- [Implemented Algorithms, Search Spaces, and Problems](#4-implemented-algorithms-search-spaces-and-problems)\n  - [Implemented Algorithms](#41-implemented-algorithms)\n  - [Implemented Search Spaces](#42-implemented-search-spaces-and-operators)\n  - [Implemented Problems](#43-implemented-problems)\n- [Data Formats](#5-data-formats)\n  - [Log Files](#51-log-files)\n  - [End Results CSV Files](#52-end-result-csv-files)\n  - [End Result Statistics CSV Files](#53-end-result-statistics-csv-files)\n- [Evaluating Experiments](#6-evaluating-experiments)\n  - [Exporting Data](#61-exporting-data)\n  - [Progress Plots](#62-progress-plots)\n  - [End Results Plots](#63-end-results-plot)\n  - [ECDF Plots](#64-ecdf-plots)\n  - [Expected Running Time Plots](#65-expected-running-time-ert-plots)\n  - [ERT-ECDF Plots](#66-ert-ecdf-plots)\n  - [Performance over Algorithm Parameter or Instance Feature](#67-performance-over-algorithm-parameter-or-instance-feature)\n  - [End Results Tables](#68-end-results-table)\n  - [Tables of Tests Comparing End Results](#69-testing-end-results-for-statistically-significant-differences-table)\n- [Examples](#7-examples)\n- [More Features](#8-more-features)\n  - [Unit Tests and Static Analysis](#81-unit-tests-and-static-analysis)\n  - [Reproducibility](#82-reproducibility)\n  - [Parallel and Distributed Experiments](#83-parallel-and-distributed-experiments)\n- [Uselful Links and References](#9-useful-links-and-references)\n- [Publications on moptipy and Works using moptipy](#10-publications-on-moptipy-and-works-using-moptipy)\n- [License](#11-license)\n- [Contact](#12-contact)\n\n\n## 1. Introduction\n\n[`moptipy`](https://thomasweise.github.io/moptipy/_static/moptipy_flyer.pdf) is a library with implementations of metaheuristic optimization methods in Python\u0026nbsp;3.12 that also offers an environment for replicable experiments.\nMetaheuristic optimization algorithms are methods for solving hard problems.\n`moptipy` provides an [API](https://thomasweise.github.io/moptipy/moptipy.api.html), several [algorithm implementations](https://thomasweise.github.io/moptipy/moptipy.algorithms.html#module-moptipy.algorithms), as well as [experiment execution](https://thomasweise.github.io/moptipy/moptipy.api.html#module-moptipy.api.experiment) and [evaluation](https://thomasweise.github.io/moptipy/moptipy.evaluation.html#module-moptipy.evaluation) facilities for metaheuristics.\n[Here](http://thomasweise.github.io/talks/moptipy.pdf) you can find a small presentation outlining some of the concepts of the system.\n[`moptipy`](https://thomasweise.github.io/moptipy/_static/moptipy_flyer.pdf) is structured with performance, ease-of-use, and generality in mind, but also based on an educational and research perspective.\nIt is therefore (hopefully) suitable for practical industrial applications, scientific research, and for students who are just entering the field of metaheuristic optimization.\n\nA metaheuristic algorithm can be a black-box method, which can solve problems without deeper knowledge about their nature.\nSuch a black-box algorithm only requires methods to [create](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.operators.Op0) and [modify](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.operators.Op1) points in the [search space](https://thomasweise.github.io/moptipy/moptipy.api.html#module-moptipy.api.space) and to [evaluate their quality](https://thomasweise.github.io/moptipy/moptipy.api.html#module-moptipy.api.objective).\nWith these operations, it will try to discover better solutions step-by-step.\nBlack-box metaheuristics are very general and can be adapted to almost any optimization problem.\nThey allow us to plug in almost arbitrary [search operators](https://thomasweise.github.io/moptipy/moptipy.api.html#module-moptipy.api.operators), [search spaces](https://thomasweise.github.io/moptipy/moptipy.api.html#module-moptipy.api.space), and [objective functions](https://thomasweise.github.io/moptipy/moptipy.api.html#module-moptipy.api.objective).\nBut it is also possible to develop algorithms that are tailored to specified problems.\nFor example, one could either design the search operators and the optimization algorithm as a unit.\nThen, the algorithm could change its way to sample new points based on the information it gathers.\nOr one could design an algorithm for a specific search space, say, the [`n`-dimensional real numbers](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.vector.html), which could then make use of the special features of this space, such as arithmetic and geometric relationships of the points within it.\nOr one could design an algorithm for a specific problem, making use of specific features of the objective function.\nFinally, there are [multi-objective optimization problems](https://thomasweise.github.io/moptipy/moptipy.api.html#module-moptipy.api.mo_problem) where multiple, potentially conflicting, criteria need to be optimized at once.\n\nWithin our [`moptipy`](https://thomasweise.github.io/moptipy/_static/moptipy_flyer.pdf) framework, you can implement algorithms of all of these types under a unified [API](https://thomasweise.github.io/moptipy/moptipy.api.html).\nOur package already provides a growing set of [algorithms](#41-implemented-algorithms) and adaptations to different [search spaces](#42-implemented-search-spaces-and-operators) as well as a set of well-known [optimization problems](#43-implemented-problems).\nWhat `moptipy` *also* offers is an [experiment execution facility](https://thomasweise.github.io/moptipy/moptipy.api.html#module-moptipy.api.experiment) that can collect detailed [log information](#5-data-formats) and [evaluate](#6-evaluating-experiments) the gathered results in a [*reproducible* fashion](#82-reproducibility).\nThe `moptipy` API supports both single-objective and multi-objective optimization.\nA set of \"[How-Tos](#3-how-tos)\" is given in [Section 3](#3-how-tos) and a longer [list of examples](#7-examples) is given in [Section 7](#7-examples).\nYou can also take a look at our [`moptipy` flyer](https://thomasweise.github.io/moptipy/_static/moptipy_flyer.pdf).\nExamples and practical applications of `moptipy` can be found in the [`moptipyapps`](https://thomasweise.github.io/moptipyapps) package, which is available on [GitHub](https://github.com/thomasWeise/moptipyapps) and in [`PyPi`](https://pypi.org/project/moptipyapps).\n\n\n## 2. Installation\n\nIn order to use this package and to, e.g., run the example codes, you need to first install it using [`pip`](https://pypi.org/project/pip) or some other tool that can install packages from [PyPi](https://pypi.org).\nYou can install the newest version of this library from [PyPi](https://pypi.org/project/moptipy) using [`pip`](https://pypi.org/project/pip) by doing\n\n```shell\npip install moptipy\n```\n\nThis will install the latest official release of our package as well as [all dependencies](https://thomasweise.github.io/moptipy/requirements_txt.html).\nIf you want to install the latest source code version from GitHub (which may not yet be officially released), you can do\n\n```shell\npip install git+https://github.com/thomasWeise/moptipy.git\n```\n\nIf you want to install the latest source code version from GitHub (which may not yet be officially released) and you have set up a private/public key for GitHub, you can also do:\n\n```shell\ngit clone ssh://git@github.com/thomasWeise/moptipy\npip install moptipy\n```\n\nThis may sometimes work better if you are having trouble reaching GitHub via `https` or `http`.\nAll dependencies for using and running `moptipy` are listed at [here](https://thomasweise.github.io/moptipy/requirements_txt.html).\nThe additional dependencies for a [full `make` build](https://thomasweise.github.io/moptipy/Makefile.html), including unit tests, static analysis, and the generation of documentation are listed [here](https://thomasweise.github.io/moptipy/requirements-dev_txt.html).\n\n\n## 3. How-Tos\n\nYou can find many examples of how to use the `moptipy` library in the folder \"[`examples`](https://github.com/thomasWeise/moptipy/tree/main/examples)\".\nHere, we talk mainly about directly applying one or multiple [optimization algorithm(s)](#41-implemented-algorithms) to one or multiple [optimization problem](#43-implemented-problems) instance(s).\nIn [Section 5 on Data Formats](#5-data-formats), we give examples and specifications of the log files that our system produces and how you can export the data to other formats.\nLater, in [Section 6 on Evaluating Experiments](#6-evaluating-experiments), we provide several examples on how to evaluate and visualize the results of experiments.\nIn [Section 7 on examples](#7-examples), we list all the examples that ship with moptipy.\n\n\n### 3.1. How to Apply 1 Optimization Algorithm Once to 1 Problem Instance\n\nThe most basic task that we can do in the domain of optimization is to apply one [algorithm](#41-implemented-algorithms) to one instance of an [optimization problem](#43-implemented-problems).\nIn our framework, we refer to this as an \"execution.\"\nYou can prepare an execution using the class [`Execution`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.execution.Execution) in the module [moptipy.api.execution](https://thomasweise.github.io/moptipy/moptipy.api.html#module-moptipy.api.execution).\nThis class follows the [builder design pattern](https://python-patterns.guide/gang-of-four/builder).\nA builder is basically an object that allows you to step-by-step set the parameters of another, more complicated object that should be created.\nOnce you have set all parameters, you can create the object.\nIn our case, the class [`Execution`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.execution.Execution) allows you to compose all the elements necessary for the algorithm run and then it performs it and provides you the end results of that execution.\n\nSo first, you create an instance `ex` of [`Execution`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.execution.Execution).\nThen you set the [algorithm](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.algorithm.Algorithm) that should be applied via the method [`ex.set_algorithm(...)`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.execution.Execution.set_algorithm).\nThen you set the [objective function](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.objective.Objective) via the method [`ex.set_objective(...)`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.execution.Execution.set_objective).\n\nThen, via [`ex.set_solution_space(...)`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.execution.Execution.set_solution_space) you set the solution [space](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.space.Space) that contains all possible solutions and is explored by the algorithm.\nThe solution space is an instance of the class [`Space`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.space.Space).\nIt provides all methods necessary to create a solution data structure, to copy the contents of one solution data structure to another one, to convert solution data structures to and from strings, and to verify whether a solution data structure is valid.\nIt is used by the optimization algorithm for instantiating the solution data structures and for copying them.\nIt is used internally by the `moptipy` system to automatically maintain copies of the current best solution, to check if the solutions are indeed valid once the algorithm finishes, and to convert the solution to a string to store it in the [log files](#51-log-files).\n\nIf the search and solution spaces are different, then you can also set a search [space](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.space.Space) via [`ex.set_search_space(...)`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.execution.Execution.set_search_space) and an [encoding](https://thomasweise.github.io/moptipy/moptipy.api.html#module-moptipy.api.encoding) via [`ex.set_encoding(...)`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.execution.Execution.set_encoding).\nThis is not necessary if the algorithm works directly on the solutions (as in our example below).\n\nEach application of an [optimization algorithm](https://thomasweise.github.io/moptipy/moptipy.api.html#module-moptipy.api.algorithm) to a problem instance will also be provided with a [random number generator](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.process.Process.get_random) and it *must* only use this random number generator for randomization and no other sources of randomness.\nYou can set the seed for this random number generator via [`ex.set_rand_seed(...)`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.execution.Execution.set_rand_seed).\nIf you create two identical executions and set the same seeds for both of them, the algorithms will make the same random decisions and hence should return the same results.\n\nFurthermore, you can also set the maximum number of candidate solutions that the optimization algorithm is allowed to investigate via [`ex.set_max_fes(...)`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.execution.Execution.set_max_fes), the maximum runtime budget in milliseconds via [`ex.set_max_time_millis(...)`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.execution.Execution.set_max_time_millis), and a goal objective value via [`ex.set_goal_f(...)`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.execution.Execution.set_goal_f) (the algorithm should stop after reaching it).\nNotice that optimization algorithms may not terminate unless the system tells them to, i.e., unless [`process.should_terminate()`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.process.Process.should_terminate) returns `True`, which is triggered by the termination conditions you define as stated above.\nTherefore, you should always specify at least either a maximum number of objective function evaluations or a runtime limit.\nIf you only specify a goal objective value and the algorithm cannot reach it, it may not terminate.\n\nFinally, you can also set the path to a log file via [`ex.set_log_file(...)`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.execution.Execution.set_log_file).\nIf you specify a log file, the system will automatically gather system information and collect the end result.\nVia [`ex.set_log_improvements(True)`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.execution.Execution.set_log_improvements), you can instruct the system to also collect the progress of the algorithm in terms of improving moves by default.\nIn the rare case that you want to log every single move that the algorithm makes, you could call [`ex.set_log_all_fes(True)`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.execution.Execution.set_log_all_fes).\nAll the collected data will be stored in a [text file](#51-log-files) *after* the algorithm has completed and you have left the process scope (see below). \n\n\u003ca href=\"https://thomasweise.github.io/moptipy/_static/process_architecture.svg\"\u003e\n\u003cimg alt=\"The process architecture of the moptipy framework.\" src=\"https://thomasweise.github.io/moptipy/_static/process_architecture.svg\" style=\"width:50%;max-width:60%;min-width:30%;float:left;padding-right:2em;padding-top:0.5em;padding-bottom:0.5em\" /\u003e\n\u003c/a\u003e\n\nAnyway, after you have completed building the execution, you can run the process you have configured via `ex.execute()`.\nThis method returns an instance of [`Process`](https://thomasweise.github.io/moptipy/moptipy.api.html#module-moptipy.api.process).\nFrom the algorithm perspective, this instance provides all the information and tools that is needed to create, copy, and evaluate solutions, as well as the termination criterion that tells it when to stop.\nFor us, the algorithm user, it provides the information about the end result, the consumed FEs, and the end result quality.\nIn the code below, we illustrate how to extract these information.\nNotice that you *must* always use the instances of `Process` in a [`with` block](https://peps.python.org/pep-0343):\nOnce this block is left, the log file will be written.\nIf you do not use a `with` block, no log file will be generated.\n\nLet us now look at a concrete example, which is also available as file [examples/single_run_rls_onemax](https://thomasweise.github.io/moptipy/examples/single_run_rls_onemax_py.html).\nAs example domain, we use [bit strings](https://thomasweise.github.io/moptipy/moptipy.spaces.html#module-moptipy.spaces.bitstrings) of length `n = 10` and try to solve the well-known [`OneMax`](https://thomasweise.github.io/moptipy/moptipy.examples.bitstrings.html#module-moptipy.examples.bitstrings.onemax) problem using the well-known [`RLS`](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.html#module-moptipy.algorithms.so.rls).\n\n```python\nfrom moptipy.algorithms.so.rls import RLS\nfrom moptipy.api.execution import Execution\nfrom moptipy.examples.bitstrings.onemax import OneMax\nfrom moptipy.operators.bitstrings.op0_random import Op0Random\nfrom moptipy.operators.bitstrings.op1_flip1 import Op1Flip1\nfrom moptipy.spaces.bitstrings import BitStrings\nfrom pycommons.io.temp import temp_file\n\nspace = BitStrings(10)  # search in bit strings of length 10\nproblem = OneMax(10)  # we maximize the number of 1 bits\nalgorithm = RLS(  # create RLS that\n    Op0Random(),  # starts with a random bit string and\n    Op1Flip1())  # flips exactly one bit in each step\n\n# We work with a temporary log file which is automatically deleted after this\n# experiment. For a real experiment, you would not use the `with` block and\n# instead put the path to the file that you want to create into `tf` by doing\n# `from pycommons.io.path import Path; tf = Path(\"mydir/my_file.txt\")`.\nwith temp_file() as tf:  # create temporary file `tf`\n    ex = Execution()  # begin configuring execution\n    ex.set_solution_space(space)  # set solution space\n    ex.set_objective(problem)  # set objective function\n    ex.set_algorithm(algorithm)  # set algorithm\n    ex.set_rand_seed(199)  # set random seed to 199\n    ex.set_log_file(tf)  # set log file = temp file `tf`\n    ex.set_max_fes(100)  # allow at most 100 function evaluations\n    with ex.execute() as process:  # now run the algorithm*problem combination\n        end_result = process.create()  # create empty record to receive result\n        process.get_copy_of_best_y(end_result)  # obtain end result\n        print(f\"Best solution found: {process.to_str(end_result)}\")\n        print(f\"Quality of best solution: {process.get_best_f()}\")\n        print(f\"Consumed Runtime: {process.get_consumed_time_millis()}ms\")\n        print(f\"Total FEs: {process.get_consumed_fes()}\")\n\n    print(\"\\nNow reading and printing all the logged data:\")\n    print(tf.read_all_str())  # instead, we load and print the log file\n# The temp file is deleted as soon as we leave the `with` block.\n```\n\nThe output we would get from this program could look something like this:\n\n```text\nBest solution found: TTTTTTTTTT\nQuality of best solution: 0\nConsumed Runtime: 129ms\nTotal FEs: 17\n\nNow reading and printing all the logged data:\nBEGIN_STATE\ntotalFEs: 17\ntotalTimeMillis: 129\nbestF: 0\nlastImprovementFE: 17\nlastImprovementTimeMillis: 129\nEND_STATE\nBEGIN_SETUP\np.name: ProcessWithoutSearchSpace\np.class: moptipy.api._process_no_ss._ProcessNoSS\np.maxFEs: 100\np.goalF: 0\np.randSeed: 199\n...\nEND_SETUP\nBEGIN_SYS_INFO\n...\nEND_SYS_INFO\nBEGIN_RESULT_Y\nTTTTTTTTTT\nEND_RESULT_Y\n```\n\nYou can also compare this output to the [example for log files](#513-example-for-single-objective-optimization) further down this text.\n\n\n### 3.2. How to Run a Series of Experiments\n\nWhen we develop algorithms or do research, then we cannot just apply an algorithm once to a problem instance and call it a day.\nInstead, we will apply multiple algorithms (or algorithm setups) to multiple problem instances and execute several runs for each algorithm * instance combination.\nOur system of course also provides the facilities for this.\n\nThe concept for this is rather simple.\nWe distinguish \"instances\" and \"setups.\"\nAn \"instance\" can be anything that a represents one specific problem instance.\nIt could be a string with its identifying name, it could be the [objective function](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.objective.Objective) itself, or a data structure with the instance data (as is the case for the Job Shop Scheduling Problem used in our book, where we use the class [Instance](https://thomasweise.github.io/moptipy/moptipy.examples.jssp.html#module-moptipy.examples.jssp.instance)).\nThe important thing is that the `__str__` method of the instance object will return a short string that can be used in file names of [log files](#51-log-files).\n\nThe second concept to understand here are \"setups.\"\nA \"setup\" is basically an almost fully configured [`Execution`](https://thomasweise.github.io/moptipy/moptipy.api.html#module-moptipy.api.execution) (see the [previous section](#31-how-to-apply-1-optimization-algorithm-once-to-1-problem-instance) for a detailed discussion of Executions.)\nThe only things that need to be left blank are the log file path and random seed, which will be filled automatically by our system.\n\nYou will basically provide a sequence of [`Callable`](https://docs.python.org/3/library/typing.html#typing.Callable)s, i.e., functions or [lambda](https://docs.python.org/3/reference/expressions.html#lambda)s, each of which will return one \"instance.\"\nAdditionally, you provide a sequence of callables (functions or lambdas), each of which receiving one \"instance\" as input and should return an almost fully configured [`Execution`](https://thomasweise.github.io/moptipy/moptipy.api.html#module-moptipy.api.execution).\nYou also provide the number of runs to be executed per \"setup\" * \"instance\" combination and a base directory path identifying the directory where one log file should be written for each run.\n\n`moptipy` also supports [parallel and distributed experiments](#83-parallel-and-distributed-experiments). \n\nAll of this is passed to the function [`run_experiment`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.experiment.run_experiment) in module [`moptipy.api.experiment`](https://thomasweise.github.io/moptipy/moptipy.api.html#module-moptipy.api.experiment).\n\nThis function will do all the work and generate a [folder structure](#511-file-names-and-folder-structure) of log files.\nIt will spawn the right number of processes, use your functions to generate \"instances\" and \"setups,\" and execute them.\nIt will also [automatically determine](https://thomasweise.github.io/moptipy/moptipy.utils.html#moptipy.utils.nputils.rand_seeds_from_str) the random seed for each run.\nThe seed sequence is determined from the instance name using a [deterministic procedure](https://thomasweise.github.io/moptipy/moptipy.utils.html#moptipy.utils.nputils.rand_seeds_from_str) and therefore [reproducible](#82-reproducibility).\nThe random seed sequence per instance will be the same for all algorithm setups.\nThis means that different algorithms would still start with the same solutions if they sample the first solution in the same way.\n\nThe system will even do \"warmup\" runs, i.e., very short dummy runs with the algorithms that are just used to make sure that the interpreter has seen all code before actually doing the experiments.\nThis avoids situations where the first actual run is slower than the others due to additional interpreter action, i.e., it reduces the bias of time measurements.\n\nBelow, we show one example for the automated experiment execution facility, which applies two algorithms to four problem instances with five runs per setup.\nWe use again the  [bit strings domain](https://thomasweise.github.io/moptipy/moptipy.spaces.html#module-moptipy.spaces.bitstrings).\nWe explore two problems ([`OneMax`](https://thomasweise.github.io/moptipy/moptipy.examples.bitstrings.html#module-moptipy.examples.bitstrings.onemax) and [`LeadingOnes`](https://thomasweise.github.io/moptipy/moptipy.examples.bitstrings.html#module-moptipy.examples.bitstrings.leadingones)) of two different sizes each, leading to four problem instances in total.\nWe apply the well-known [`RLS`](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.html#module-moptipy.algorithms.so.rls) as well as the trivial [random sampling](https://thomasweise.github.io/moptipy/moptipy.algorithms.html#module-moptipy.algorithms.random_sampling).\n\nThe code below is available as file [examples/experiment_2_algorithms_4_problems](https://thomasweise.github.io/moptipy/examples/experiment_2_algorithms_4_problems_py.html).\nBesides executing the experiment, it also prints the end results obtained from parsing the log files (see [Section 5.2.](#52-end-result-csv-files) for more information).\n\n```python\nfrom moptipy.algorithms.so.rls import RLS\nfrom moptipy.algorithms.random_sampling import RandomSampling\nfrom moptipy.api.execution import Execution\nfrom moptipy.api.experiment import run_experiment\nfrom moptipy.evaluation.end_results import from_logs\nfrom moptipy.examples.bitstrings.leadingones import LeadingOnes\nfrom moptipy.examples.bitstrings.onemax import OneMax\nfrom moptipy.operators.bitstrings.op0_random import Op0Random\nfrom moptipy.operators.bitstrings.op1_flip1 import Op1Flip1\nfrom moptipy.spaces.bitstrings import BitStrings\nfrom pycommons.io.temp import temp_dir\n\n# The four problems we want to try to solve:\nproblems = [lambda: OneMax(10),  # 10-dimensional OneMax\n            lambda: OneMax(32),  # 32-dimensional OneMax\n            lambda: LeadingOnes(10),  # 10-dimensional LeadingOnes\n            lambda: LeadingOnes(32)]  # 32-dimensional LeadingOnes\n\n\ndef make_rls(problem) -\u003e Execution:\n    \"\"\"\n    Create an RLS Execution.\n\n    :param problem: the problem (OneMax or LeadingOnes)\n    :returns: the execution\n    \"\"\"\n    ex = Execution()\n    ex.set_solution_space(BitStrings(problem.n))\n    ex.set_objective(problem)\n    ex.set_algorithm(RLS(  # create RLS that\n        Op0Random(),  # starts with a random bit string and\n        Op1Flip1()))  # flips one bit in each step\n    ex.set_max_fes(100)  # permit 100 FEs\n    return ex\n\n\ndef make_random_sampling(problem) -\u003e Execution:\n    \"\"\"\n    Create a Random Sampling Execution.\n\n    :param problem: the problem (OneMax or LeadingOnes)\n    :returns: the execution\n    \"\"\"\n    ex = Execution()\n    ex.set_solution_space(BitStrings(problem.n))\n    ex.set_objective(problem)\n    ex.set_algorithm(RandomSampling(Op0Random()))\n    ex.set_max_fes(100)\n    return ex\n\n\n# We execute the whole experiment in a temp directory.\n# For a real experiment, you would put an existing directory path in `td`\n# by doing `from pycommons.io.path import Path; td = directory_path(\"mydir\")`\n# and not use the `with` block.\nwith temp_dir() as td:  # create temporary directory `td`\n    run_experiment(base_dir=td,  # set the base directory for log files\n                   instances=problems,  # define the problem instances\n                   setups=[make_rls,  # provide RLS run creator\n                           make_random_sampling],  # provide RS run creator\n                   n_runs=5)  # we will execute 5 runs per setup\n\n    for er in from_logs(td):  # parse all log files and print end results\n        print(f\"{er.algorithm} on {er.instance}: {er.best_f}\")\n# The temp directory is deleted as soon as we leave the `with` block.\n```\n\nThe output of this program, minus the status information, could look roughly like this:\n\n```text\nrs on onemax_10: 0\nrs on onemax_10: 2\nrs on onemax_10: 1\nrs on onemax_10: 2\nrs on onemax_10: 1\nrs on onemax_32: 8\nrs on onemax_32: 8\nrs on onemax_32: 8\nrs on onemax_32: 9\nrs on onemax_32: 9\nrs on leadingones_32: 26\nrs on leadingones_32: 26\nrs on leadingones_32: 25\nrs on leadingones_32: 26\nrs on leadingones_32: 23\nrs on leadingones_10: 4\nrs on leadingones_10: 0\nrs on leadingones_10: 3\nrs on leadingones_10: 3\nrs on leadingones_10: 0\nrls_flip1 on onemax_10: 0\nrls_flip1 on onemax_10: 0\nrls_flip1 on onemax_10: 0\nrls_flip1 on onemax_10: 0\nrls_flip1 on onemax_10: 0\nrls_flip1 on onemax_32: 2\nrls_flip1 on onemax_32: 1\nrls_flip1 on onemax_32: 2\nrls_flip1 on onemax_32: 2\nrls_flip1 on onemax_32: 1\nrls_flip1 on leadingones_32: 18\nrls_flip1 on leadingones_32: 23\nrls_flip1 on leadingones_32: 28\nrls_flip1 on leadingones_32: 16\nrls_flip1 on leadingones_32: 29\nrls_flip1 on leadingones_10: 0\nrls_flip1 on leadingones_10: 0\nrls_flip1 on leadingones_10: 0\nrls_flip1 on leadingones_10: 0\nrls_flip1 on leadingones_10: 0\n```\n\nYou can simply launch the main process several times in parallel in the same folder to achieve parallelism.\nActually, you can also execute experiments in a *distributed* fashion like this:\nAll you have to do is to share the folder for the log files among all computer nodes.\nThen, in this shared folder, execute the experiment on each node.\nThe system will then automatically ensure that no work is done twice and the experiment runs in a distributed fashion with almost no overhead.\n\nThe trick is that we create the random seeds in a deterministic fashion so that each experiment on each node will have the same seeds and, hence, the [same names for the log files](#511-file-names-and-folder-structure).\nThe log files are created emptily right before a run starts and filled with data once the run is completed.\nSince file creation is atomic in distributed file systems, the system can then automatically ensure that no run is performed by more than one node.\nThis is an extremely simple yet very robust method for distribution with very low overhead.\n\n\n### 3.3. How to Solve an Optimization Problem\n\nIf you want to solve an optimization problem with [moptipy](https://thomasweise.github.io/moptipy), then you need at least the following three things:\n\n1. a space `Y` of possible solutions,\n2. an objective function `f`  rating the solutions, i.e., which maps elements `y` of `Y` to either integer or float numbers, where *smaller* values are better, and\n3. an optimization algorithm that navigates through `Y` and tries to find solutions `y` in `Y` with low corresponding values `f(y)`.\n\nYou may need more components, but if you have these three, then you can [run an experiment](#32-how-to-run-a-series-of-experiments).\n\n\n#### 3.3.1. Define a New Problem Type\n\nAt the core of all optimization problems lies the objective function.\nAll objective functions in [`moptipy`](https://thomasweise.github.io/moptipy) are instances of the class [Objective](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.objective.Objective).\nIf you want to add a new optimization problem, you must derive a new subclass from this class. \n\nThere are two functions you must be implemented:\n\n- [`evaluate(x)`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.objective.Objective.evaluate) receives a candidate solution `x` as input and must return either an `int` or a `float` rating its quality (smaller values are *better*) and\n- `__str__()` returns a string representation of the objective function and may be used in file names and folder structures (depending on how you execute your experiments).\n  It therefore must not contain spaces and other dodgy characters.\n\nAdditionally, you *may* implement the following two functions\n\n- [`lower_bound()`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.objective.Objective.lower_bound) returns either an `int` or a `float` with the lower bound of the objective value.\n  This value does not need to be an objective value that can actually be reached, but if you implement this function, then the value must be small enough so that it is *impossible* to ever reach a smaller objective value.\n  If we execute an experiment and no goal objective value is specified, then the system will automatically use this lower bound if it is present.\n  Then, if any solution `x` with `f.evaluate(x)==f.lower_bound()` is encountered, the optimization process is automatically stopped.\n  Furthermore, after the optimization process is stopped, it is verified that the final solution does not have an objective value smaller than the lower bound.\n  If it does, then we throw an exception.\n- [`upper_bound()`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.objective.Objective.upper_bound) returns either an `int` or a `float` with the upper bound of the objective value.\n  This value does not need to be an objective value that can actually be reached, but if you implement this function, then the value must be large enough so that it is *impossible* to ever reach a larger objective value.\n  This function, if present, is used to validate the objective value of the final result of the optimization process.\n\nOK, with this information we are basically able to implement our own problem.\nHere, we define the task \"sort n numbers\" as optimization problem.\nBasically, we want that our optimization algorithm works on [permutations](https://thomasweise.github.io/moptipy/moptipy.spaces.html#module-moptipy.spaces.permutations) of `n` numbers and is searching for the sorted permutation.\nAs objective value, we count the number of \"sorting errors\" in a permutation.\nIf the number at index `i` is bigger than the number at index `i+1`, then this is a sorting error.\nIf `n=5`, then the permutation `0;1;2;3;4` has no sorting error, i.e., the best possible objective value `0`.\nThe permutation `4;3;2;1;0` has `n-1=4` sorting errors, i.e., is the worst possible solution.\nThe permutation `3;4;2;0;1` as `2` sorting errors.\n\nFrom these thoughts, we also know that we can implement `lower_bound()` to return `0` and `upper_bound()` to return `n-1`.\n`__str__` could be `\"sort\" + n`, i.e., `sort5` in the above example where `n=5`.\n\nWe provide the corresponding code in [Section 3.3.3](#333-applying-an-own-algorithm-to-an-own-problem) below.\n\n\n#### 3.3.2. Define a New Algorithm\n\nWhile [`moptipy`](https://thomasweise.github.io/moptipy) comes with several [well-known algorithms](#41-implemented-algorithms) out-of-the-box, you can of course also implement your own [algorithms](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.algorithm.Algorithm).\nThese can then make use of the existing [spaces](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.space.Space) and [search operators](https://thomasweise.github.io/moptipy/moptipy.api.html#module-moptipy.api.operators) \u0026ndash; or not.\nLet us here create an example algorithm implementation that does *not* use any of the pre-defined [search operators](https://thomasweise.github.io/moptipy/moptipy.api.html#module-moptipy.api.operators).\n\nAll optimization algorithms must be subclasses of the class [Algorithm](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.algorithm.Algorithm).\nEach of them must implement two methods, as described in the [documentation](https://thomasweise.github.io/moptipy/moptipy.api.html#module-moptipy.api.algorithm):\n\n- [`solve(process)`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.algorithm.Algorithm.solve) receives an instance of [`Process`](https://thomasweise.github.io/moptipy/moptipy.api.html#module-moptipy.api.process), which provides the operations to work with the search space, to evaluate solutions, the termination criterion, and the random number generator.\n- `__str__()` must return a short string representation identifying the algorithm and its setup.\n  This string will be used in file and folder names and therefore must not contain spaces or otherwise dodgy characters.\n\nAdditionally, you may need to implement the following methods if the algorithm has other components:\n\n- [`initialize()`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.component.Component.initialize) initializes all sub-components of the algorithms and is called *before* each run.\n  The base class [`Component`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.component.Component), from which all elements of the optimization API are derived, already has this method.\n  If a new algorithm uses, for example, a [selection](https://thomasweise.github.io/moptipy/moptipy.algorithms.modules.html#module-moptipy.algorithms.modules.selection) algorithm, a [temperature schedule](https://thomasweise.github.io/moptipy/moptipy.algorithms.modules.html#module-moptipy.algorithms.modules.selection), or a [search operator](https://thomasweise.github.io/moptipy/moptipy.api.html#module-moptipy.api.operators), it needs to invoke the `initialize()` methods of these components from its own `initialize()` method.\n- [`log_parameters_to(...)`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.component.Component.log_parameters_to) is used to store all the configuration parameters of an algorithm to a [log section](https://thomasweise.github.io/moptipy/moptipy.utils.html#moptipy.utils.logger.KeyValueLogSection).\n  If the algorithm has any sub-components, it must here invoke the `log_parameters_to(...)` method of these components.\n  In this case, it can pass different [prefix scopes](https://thomasweise.github.io/moptipy/moptipy.utils.html#moptipy.utils.logger.KeyValueLogSection.scope) to the `log_parameters_to(...)` methods of its components.\n  Then, different prefixes can be added to each component's parameter keys, ensuring that all keys are unique. \n\nThe instance `process` of [`Process`](https://thomasweise.github.io/moptipy/moptipy.api.html#module-moptipy.api.process) passed to the function [`solve`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.algorithm.Algorithm.solve) is a key element of our [`moptipy`](https://thomasweise.github.io/moptipy) API.\nIf the algorithm needs a data structure to hold a point in the search space, it should invoke [`process.create()`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.space.Space.create).\nIf it needs to copy the point `source` to the point `dest`, it should invoke [`process.copy(dest, source)`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.space.Space.copy).\n\nIf it wants to know the quality of the point `x`, it should invoke [`process.evaluate(x)`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.process.Process.evaluate).\nThis function will forward the call to the actual objective function (see, e.g., [Section 3.3.1](#331-define-a-new-problem-type) above).\nHowever, it will do more:\nIt will automatically keep track of the best-so-far solution and, if needed, build logging information in memory.\n\nBefore every single call to [`process.evaluate()`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.process.Process.evaluate), you should invoke [`process.should_terminate()`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.process.Process.should_terminate).\nThis function returns `True` if the optimization algorithm should stop whatever it is doing and return.\nThis can happen when a solution of sufficiently good quality is reached, when the maximum number of FEs is exhausted, or when the computational budget in terms of runtime is exhausted.\n\nSince many optimization algorithms make random choices, the function [`process.get_random()`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.process.Process.get_random) returns a [random number generator](https://numpy.org/doc/stable/reference/random/generator.html).\nThis generator *must* be the only source of randomness used by an algorithm.\nIt will automatically be seeded by our system, allowing for [repeatable and reproducible](https://www.acm.org/publications/policies/artifact-review-and-badging-current) runs.\n\nThe `process` also can provide information about the best-so-far [solution](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.process.Process.get_copy_of_best_y) or [point in the search space](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.process.Process.get_copy_of_best_x), the consumed [runtime](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.process.Process.get_consumed_time_millis) and [FEs](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.process.Process.get_consumed_fes), as well as [when the last improvement was achieved](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.process.Process.get_last_improvement_fe).\nAnyway, all interaction between the algorithm and the actual optimization algorithm will happen through the `process` object.\n\nEquipped with this information, we can develop a simple and rather stupid algorithm to attack the sorting problem.\nThe search space that we use are the [permutations](https://thomasweise.github.io/moptipy/moptipy.spaces.html#module-moptipy.spaces.permutations) of `n` numbers.\n(These will be internally represented as [numpy `ndarray`s](https://numpy.org/doc/stable/reference/arrays.ndarray.html), but we do not need to bother with this, as we this is done automatically for us.) \nOur algorithm should start with allocating a point `x_cur` in the search space, filling it with the numbers `0..n-1`, and shuffling it randomly (because we want to start at a random solution).\nFor the shuffling, it will use than random number generator provided by `process`.\nIt will evaluate this solution and remember its quality in variable `f_cur`.\nIt will also allocate a second container `x_new` for permutations.\n\nIn each step, our algorithm will copy `x_cur` to `x_new`.\nThen, it will use the random number generator to draw two numbers `i` and `j` from `0..n-1`.\nIt will swap the two numbers at these indices in `x_new`, i.e., exchange `x_new[i], x_new[j] = x_new[j], x_new[i]`.\nWe then evaluate `x_new` and if the resulting objective value `f_new` is better than `f_cur`, we swap `x_new` and `x_cur` (which is faster than copying `x_new` to `x_cur`) and store `f_new` in `f_cur`.\nWe repeat this until [`process.should_terminate()`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.process.Process.should_terminate) becomes `True`.\nAll of this is implemented in the source code example below in [Section 3.3.3](#333-applying-an-own-algorithm-to-an-own-problem).\n\nFinally, as a side note:\nOur system can automatically store the results of optimization processes in [log file](#51-log-files).\nThe `process` API also allows your algorithm to store additional information in these files:\nFirst, you can check with [`process.has_log()`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.process.Process.has_log) if the process was configured to store information in a log file.\nIf this function returns `True`, then you can create a [section](#512-log-file-sections) with a given `title` in the log files that should contain one single string `text` by calling  [`process.add_log_section(title, text)`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.process.Process.add_log_section).\nMake sure that all section `title`s are unique.\nAll such sections will be appended at the end of the log files, wrapped in `BEGIN_title` and `END_title` markers, as prescribed by [our log file format](#512-log-file-sections).\n\n\n#### 3.3.3. Applying an Own Algorithm to an Own Problem\n\nThe following code combines our [own algorithm](#332-define-a-new-algorithm) and our [own problem type](#331-define-a-new-problem-type) that we discussed in the prior two sections and executes an experiment.\nIt is available as file [examples/experiment_own_algorithm_and_problem](https://thomasweise.github.io/moptipy/examples/experiment_own_algorithm_and_problem_py.html).\nNotice how we provide functions for generating both the problem instances (here the objective functions) and the algorithm setups exactly as we described in [Section 3.2.](#32-how-to-run-a-series-of-experiments) above.\n\n```python\nfrom moptipy.api.algorithm import Algorithm\nfrom moptipy.api.execution import Execution\nfrom moptipy.api.experiment import run_experiment\nfrom moptipy.api.objective import Objective\nfrom moptipy.api.process import Process\nfrom moptipy.evaluation.end_results import from_logs\nfrom moptipy.spaces.permutations import Permutations\nfrom pycommons.io.temp import temp_dir\n\n\nclass MySortProblem(Objective):\n    \"\"\"An objective function that rates how well a permutation is sorted.\"\"\"\n\n    def __init__(self, n: int) -\u003e None:\n        \"\"\"\n        Initialize: Set the number of values to sort.\n\n        :param n: the scale of the problem\n        \"\"\"\n        super().__init__()\n        #: the number of numbers to sort\n        self.n = n\n\n    def evaluate(self, x) -\u003e int:\n        \"\"\"\n        Compute how often a bigger number follows a smaller one.\n\n        :param x: the permutation\n        \"\"\"\n        errors = 0  # we start at zero errors\n        for i in range(self.n - 1):  # for i in 0..n-2\n            if x[i] \u003e x[i + 1]:  # that's a sorting error!\n                errors += 1  # so we increase the number\n        return errors  # return result\n\n    def lower_bound(self) -\u003e int:\n        \"\"\"\n        Get the lower bound: 0 errors is the optimum.\n\n        Implementing this function is optional, but it can help in two ways:\n        First, the optimization processes can be stopped automatically when a\n        solution of this quality is reached. Second, the lower bound is also\n        checked when the end results of the optimization process are verified.\n\n        :returns: 0\n        \"\"\"\n        return 0\n\n    def upper_bound(self) -\u003e int:\n        \"\"\"\n        Get the upper bound: n-1 errors is the worst.\n\n        Implementing this function is optional, but it can help, e.g., when\n        the results of the optimization process are automatically checked.\n\n        :returns: n-1\n        \"\"\"\n        return self.n - 1\n\n    def __str__(self):\n        \"\"\"\n        Get the name of this problem.\n\n        This name is used in the directory structure and file names of the\n        log files.\n\n        :returns: \"sort\" + n\n        \"\"\"\n        return f\"sort{self.n}\"\n\n\nclass MyAlgorithm(Algorithm):\n    \"\"\"An example for a simple rigidly structured optimization algorithm.\"\"\"\n\n    def solve(self, process: Process) -\u003e None:\n        \"\"\"\n        Solve the problem encapsulated in the provided process.\n\n        :param process: the process instance which provides random numbers,\n            functions for creating, copying, and evaluating solutions, as well\n            as the termination criterion\n        \"\"\"\n        random = process.get_random()  # get the random number generator\n        x_cur = process.create()  # create the record for the current solution\n        x_new = process.create()  # create the record for the new solution\n        n = len(x_cur)  # get the scale of problem as length of the solution\n\n        x_cur[:] = range(n)  # We start by initializing the initial solution\n        random.shuffle(x_cur)  # as [0...n-1] and then randomly shuffle it.\n        f_cur = process.evaluate(x_cur)  # compute solution quality\n\n        while not process.should_terminate():  # repeat until we are finished\n            process.copy(x_new, x_cur)  # copy current to new solution\n            i = random.integers(n)  # choose the first random index\n            j = random.integers(n)  # choose the second random index\n            x_new[i], x_new[j] = x_new[j], x_new[i]  # swap values at i and j\n            f_new = process.evaluate(x_new)  # evaluate the new solution\n            if f_new \u003c f_cur:  # if it is better than current solution\n                x_new, x_cur = x_cur, x_new  # swap current and new solution\n                f_cur = f_new  # and remember quality of new solution\n\n    def __str__(self):\n        \"\"\"\n        Get the name of this algorithm.\n\n        This name is then used in the directory path and file name of the\n        log files.\n\n        :returns: myAlgo\n        \"\"\"\n        return \"myAlgo\"\n\n\n# The four problems we want to try to solve:\nproblems = [lambda: MySortProblem(5),  # sort 5 numbers\n            lambda: MySortProblem(10),  # sort 10 numbers\n            lambda: MySortProblem(100)]  # sort 100 numbers\n\n\ndef make_execution(problem) -\u003e Execution:\n    \"\"\"\n    Create an application of our algorithm to our problem.\n\n    :param problem: the problem (MySortProblem)\n    :returns: the execution\n    \"\"\"\n    ex = Execution()\n    ex.set_solution_space(\n        Permutations.standard(problem.n))  # we use permutations of [0..n-1]\n    ex.set_objective(problem)  # set the objective function\n    ex.set_algorithm(MyAlgorithm())  # apply our algorithm\n    ex.set_max_fes(100)  # permit 100 FEs\n    return ex\n\n\n# We execute the whole experiment in a temp directory.\n# For a real experiment, you would put an existing directory path in `td`\n# by doing `from pycommons.io.path import Path; td = directory_path(\"mydir\")`\n# and not use the `with` block.\nwith temp_dir() as td:  # create temporary directory `td`\n    run_experiment(base_dir=td,  # set the base directory for log files\n                   instances=problems,  # define the problem instances\n                   setups=[make_execution],  # creator for our algorithm\n                   n_runs=5)  # we will execute 5 runs per setup\n    for er in from_logs(td):  # parse all log files and print end results\n        print(f\"{er.algorithm} on {er.instance}: {er.best_f}\")\n# The temp directory is deleted as soon as we leave the `with` block.\n```\n\nThe output of this program, minus status output, could look like this:\n\n```text\nmyAlgo on sort10: 2\nmyAlgo on sort10: 2\nmyAlgo on sort10: 1\nmyAlgo on sort10: 1\nmyAlgo on sort10: 2\nmyAlgo on sort100: 35\nmyAlgo on sort100: 41\nmyAlgo on sort100: 33\nmyAlgo on sort100: 34\nmyAlgo on sort100: 35\nmyAlgo on sort5: 1\nmyAlgo on sort5: 1\nmyAlgo on sort5: 1\nmyAlgo on sort5: 1\nmyAlgo on sort5: 1\n```\n\n## 4. Implemented Algorithms, Search Spaces, and Problems\n\nHere we list the [algorithms](#41-implemented-algorithms), [search spaces](#42-implemented-search-spaces-and-operators), and [optimization problems](#43-implemented-problems) that we implement in our [`moptipy`](https://thomasweise.github.io/moptipy) framework.\n\n\n### 4.1. Implemented Algorithms\n\nThe following algorithms are completely black-box and work for both single- and multi-objective optimization.\n(Well, *work* here is relative \u0026hellip; they are basically the worst possible algorithms you could choose and are only included for the sake of completeness.)\n\n- [Single Random Sample](https://thomasweise.github.io/moptipy/moptipy.algorithms.html#module-moptipy.algorithms.single_random_sample) creates and evaluates exactly one single random solution.\n- [Random Sampling](https://thomasweise.github.io/moptipy/moptipy.algorithms.html#module-moptipy.algorithms.random_sampling) keeps creating random solutions until the computational budget is exhausted.\n- [Random Walk](https://thomasweise.github.io/moptipy/moptipy.algorithms.html#module-moptipy.algorithms.random_sampling) creates a random solution and then keeps applying the unary search operator and always accepts the result.\n\n\n#### 4.1.1. Single-Objective Optimization\n\nHere we list optimization algorithms that optimize a *single* [objective function](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.objective.Objective).\n\n\n##### 4.1.1.1. Single-Objective Optimization with Arbitrary Search Spaces\n\nThe first set of algorithms is general, i.e., can work with arbitrary [search spaces](#42-implemented-search-spaces-and-operators).\n\n- The simple [Hill Climber](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.html#module-moptipy.algorithms.so.hill_climber) creates a random solution as initial best-so-far solution and then iteratively applies the unary search operator to the best-so-far solution.\n  When the result of the unary operator is better, it becomes the new best-so-far solution, otherwise it is discarded.\n- The [Hill Climber with Restarts](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.html#module-moptipy.algorithms.so.hill_climber_with_restarts) works exactly like the hill climber, but restarts at a new random solution after a fixed number of unsuccessful moves.\n- A Random Local Search ([RLS](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.html#module-moptipy.algorithms.so.rls)) also known as (1+1)\u0026nbsp;EA works like the [Hill Climber](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.html#module-moptipy.algorithms.so.hill_climber) as well, but accepts a new solution if it is *not worse* than the best-so-far solution (instead of requiring it to be strictly *better*, as the hill climber does).\n- The [(\u0026#x3BC;+\u0026#x3BB;)\u0026nbsp;EA](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.html#module-moptipy.algorithms.so.ea), where \"EA\" stands for \"Evolutionary Algorithm,\" is a simple population-based metaheuristic that starts with a population of `mu` random solutions.\n  In each iteration, it retains only the `mu` best solutions from the population (\"best\" in terms of the objective value, ties are broken such that newer solutions are preferred).\n  It then applies the unary operator and the binary operator to generate `lambda` new solutions and adds them to the population.\n  The `(1+1) EA` with `br=0` probability to use the binary operator is equivalent to [RLS](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.html#module-moptipy.algorithms.so.rls). \n- The [general EA](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.html#module-moptipy.algorithms.so.general_ea) is a generalized version of the (\u0026#x3BC;+\u0026#x3BB;)\u0026nbsp;EA that can additionally be configured with a [fitness assignment process](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.fitnesses.html) and both survival and mating [selection algorithms](https://thomasweise.github.io/moptipy/moptipy.algorithms.modules.html#module-moptipy.algorithms.modules.selection).\n- Simulated Annealing ([SA](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.html#module-moptipy.algorithms.so.simulated_annealing)) is similar to [RLS](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.html#module-moptipy.algorithms.so.rls) but sometimes accepts worsening moves.\n  The probability to accept such moves gets smaller the worse the moves are and the longer the search continues.\n- The (\u0026#x3BC;+\u0026#x3BB;)\u0026nbsp;Memetic Algorithm ([MA](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.html#module-moptipy.algorithms.so.ma)) works like the [(\u0026#x3BC;+\u0026#x3BB;)\u0026nbsp;EA](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.html#module-moptipy.algorithms.so.ea) but it applies the binary search operator at a rate of\u0026nbsp;100%.\n  (In other words, it never uses the unary search operator).\n  It refines the results of the nullary and binary search operators by using them as starting points of another algorithm, say an [SA](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.html#module-moptipy.algorithms.so.simulated_annealing) or [RLS](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.html#module-moptipy.algorithms.so.rls), which is executed for a pre-defined number of steps.\n- The above [MA](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.html#module-moptipy.algorithms.so.ma) can be configured to use [RLS](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.html#module-moptipy.algorithms.so.rls) as local search.\n  This special case is also implemented as hard-coded [MA-RLS](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.html#module-moptipy.algorithms.so.marls), which should be a little bit more speed-efficient (due to hard-coding the local search instead of plugging it in), but otherwise it takes the exactly same route through the search space.\n- The [general MA](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.html#module-moptipy.algorithms.so.general_ma) is a generalized version of the (\u0026#x3BC;+\u0026#x3BB;)\u0026nbsp;MA that can additionally be configured with a [fitness assignment process](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.fitnesses.html) and both survival and mating [selection algorithms](https://thomasweise.github.io/moptipy/moptipy.algorithms.modules.html#module-moptipy.algorithms.modules.selection).\n- The Plant Propagation Algorithm ([PPA](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.html#module-moptipy.algorithms.so.ppa)) is a population-based metaheuristic that allocates both the number of new solutions to be derived from an existing solution via the unary search operator as well as the step size to be used by this operator based on the normalized objective value of the solution.\n\n\n##### 4.1.1.2. Single-Objective Optimization with Continuous Search Space\n\nThe algorithms listed here are intended for single-objective optimization of [continuous search spaces](https://thomasweise.github.io/moptipy/moptipy.spaces.html#moptipy.spaces.vectorspace.VectorSpace).\nThey *only* work with search spaces that are instances of [`VectorSpace`](https://thomasweise.github.io/moptipy/moptipy.spaces.html#moptipy.spaces.vectorspace.VectorSpace).\nSuch spaces are defined by box-constraints over the *n*-dimensional real numbers.\n\n- The quasi-Newton method by Broyden, Fletcher, Goldfarb, and Shanno ([BFGS](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.vector.html#moptipy.algorithms.so.vector.scipy.BGFS)), wrapped from [SciPy](https://docs.scipy.org/doc/scipy/reference/optimize.minimize-bfgs.html).\n- The Conjugate Gradient ([CG](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.vector.html#moptipy.algorithms.so.vector.scipy.CG)) algorithm, wrapped from [SciPy](https://docs.scipy.org/doc/scipy/reference/optimize.minimize-cg.html).\n- The Covariance Matrix Adaptation Evolution Strategies [CMA-ES](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.vector.html#moptipy.algorithms.so.vector.cmaes_lib.CMAES), [Separable CMA-ES](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.vector.html#moptipy.algorithms.so.vector.cmaes_lib.SepCMAES), and [BIPOP-CMA-ES](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.vector.html#moptipy.algorithms.so.vector.cmaes_lib.BiPopCMAES) from the library [cmaes](https://pypi.org/project/cmaes).\n- Differential Evolution ([DE](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.vector.html#moptipy.algorithms.so.vector.scipy.DE)), wrapped from [SciPy](https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.differential_evolution.html).\n- The Downhill Simplex method based on the [Nelder-Mead](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.vector.html#moptipy.algorithms.so.vector.scipy.NelderMead), wrapped from [SciPy](https://docs.scipy.org/doc/scipy/reference/optimize.minimize-neldermead.html).\n- [Powell's Algorithm](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.vector.html#moptipy.algorithms.so.vector.scipy.Powell), wrapped from [SciPy](https://docs.scipy.org/doc/scipy/reference/optimize.minimize-powell.html).\n- The Sequential Least Squares Programming ([SLSQP](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.vector.html#moptipy.algorithms.so.vector.scipy.SLSQP) algorithm, wrapped from [SciPy](https://docs.scipy.org/doc/scipy/reference/optimize.minimize-slsqp.html)).\n- The Truncated Newton Method ([TNC](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.vector.html#moptipy.algorithms.so.vector.scipy.TNC)), wrapped from [SciPy](https://docs.scipy.org/doc/scipy/reference/optimize.minimize-tnc.html).\n\n\n#### 4.1.2. Multi-Objective Optimization\n\nThe algorithms listed here are suitable for [multi-objective optimization](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.mo_problem.MOProblem).\nIn other words, they try to minimize multiple [objective functions](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.objective.Objective) at once. \n\n- [Multi-Objective Random Local Search](https://thomasweise.github.io/moptipy/moptipy.algorithms.mo.html#moptipy.algorithms.mo.morls.MORLS) (MORLS) works exactly as [RLS](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.html#moptipy.algorithms.so.rls.RLS), but it accepts a solution if it is not dominated by the current solution.\n This is *not* a good algorithm.\n- The Fast Elitist Non-Dominated Sorting Genetic Algorithm ([NSGA-II](https://thomasweise.github.io/moptipy/moptipy.algorithms.mo.html#module-moptipy.algorithms.mo.nsga2)) is maybe the most popular multi-objective evolutionary algorithm.\n\n\n### 4.2. Implemented Search Spaces and Operators\n\n- [Bit Strings](https://thomasweise.github.io/moptipy/moptipy.spaces.html#moptipy.spaces.bitstrings.BitStrings) of a fixed length `n` are represented as `numpy` arrays as well.\n  Here, each value can either be `True` or `False`.\n  - Nullary Operators:\n    - [random initialization](https://thomasweise.github.io/moptipy/moptipy.operators.bitstrings.html#moptipy.operators.bitstrings.op0_random.Op0Random) fills the string with random bits\n  - Unary Operators:\n    - [flip 1](https://thomasweise.github.io/moptipy/moptipy.operators.bitstrings.html#moptipy.operators.bitstrings.op1_flip1.Op1Flip1) flips one single bit.\n    - [flip m/n](https://thomasweise.github.io/moptipy/moptipy.operators.bitstrings.html#moptipy.operators.bitstrings.op1_m_over_n_flip.Op1MoverNflip.op1) flips each bit independently with probability `m/n`.\n    - [flip m](https://thomasweise.github.io/moptipy/moptipy.operators.bitstrings.html#module-moptipy.operators.bitstrings.op1_flip_m) is an operator with step width where the exact number of bits to flip can be determined.\n  - Binary Operators:\n    - [uniform crossover](https://thomasweise.github.io/moptipy/moptipy.operators.bitstrings.html#moptipy.operators.bitstrings.op2_uniform.Op2Uniform) randomly chooses, for each bit, from which of the two source strings it will be copied.\n- [Permutations](https://thomasweise.github.io/moptipy/moptipy.spaces.html#module-moptipy.spaces.permutations) with and without Repetitions are represented as numpy arrays of integers.\n  A permutation of the values \"1,2,3\", for example, is an arrangement containing these values in any order.\n  In `moptipy`, the space `Permutation` is defined over any arrangement of a given base string. For example, if a base string is \"1,2,2,3\", then any arrangement containing one \"1\", two \"2\"s, and one \"3\" is then an element of this space.\n  This allows us to represent both normal permutations as well as those with repetitions.\n  - Nullary Operators:\n    - [Fisher-Yates shuffle](https://thomasweise.github.io/moptipy/moptipy.operators.permutations.html#moptipy.operators.permutations.op0_shuffle.Op0Shuffle) creates uniformly randomly distributed permutations.\n  - Unary Operators:\n    - [insert_1](https://thomasweise.github.io/moptipy/moptipy.operators.permutations.html#module-moptipy.operators.permutations.op1_insert1) removes one element from a permutation and inserts it elsewhere.\n    - [swap 2](https://thomasweise.github.io/moptipy/moptipy.operators.permutations.html#moptipy.operators.permutations.op1_swap2.Op1Swap2) swaps exactly two (different) values.\n    - [swap n](https://thomasweise.github.io/moptipy/moptipy.operators.permutations.html#moptipy.operators.permutations.op1_swapn.Op1SwapN) performs a random number of swaps.\n    - [swap_exactly_n](https://thomasweise.github.io/moptipy/moptipy.operators.permutations.html#module-moptipy.operators.permutations.op1_swap_exactly_n) is a unary operator with step size that will change/swap exactly a given number of elements for permutations where each element occurs once and which will try to swap that many in permutations with repetitions (where it might not be possible to swap exactly the required number of elements).\n    - [swap_try_n](https://thomasweise.github.io/moptipy/moptipy.operators.permutations.html#module-moptipy.operators.permutations.op1_swap_try_n) is a unary operator very similar to the `swap_exactly_n` operator, but it invests much less effort to achieve the number of prescribed swaps and thus is both much faster but also more likely to perform less swaps.\n  - Binary Operators:\n    - [generalized alternating position crossover](https://thomasweise.github.io/moptipy/moptipy.operators.permutations.html#moptipy.operators.permutations.op2_gap.Op2GeneralizedAlternatingPosition) chooses, for each index, from which of the two source permutations the (next not-yet-used) value should be copied\n    - [order-based crossover](https://thomasweise.github.io/moptipy/moptipy.operators.permutations.html#moptipy.operators.permutations.op2_ox2.Op2OrderBased) randomly selects a set of indices and copies the elements from first source permutation to the same indices in the destination string. It then copies the remaining elements from the second source, maintaining the order in which they appear in the second source string.\n- [Signed Permutations](https://thomasweise.github.io/moptipy/moptipy.spaces.html#module-moptipy.spaces.signed_permutations) with and without Repetitions are represented as numpy arrays of integers.\n   They cannot contain the value `0`. All other values may occur either positive or negative, e.g., `5` or `-5`.\n  - Nullary Operators:\n    - [shuffle-and-flip](https://thomasweise.github.io/moptipy/moptipy.operators.signed_permutations.html#module-moptipy.operators.signed_permutations.op0_shuffle_and_flip) creates uniformly randomly distributed signed permutations.\n  - Unary Operators:\n    - [swap 2 or flip](https://thomasweise.github.io/moptipy/moptipy.operators.signed_permutations.html#module-moptipy.operators.signed_permutations.op1_swap_2_or_flip) either swaps exactly two (different) values or flips the sign of one value.    \n- [Ordered Choices](https://thomasweise.github.io/moptipy/moptipy.spaces.html#module-moptipy.spaces.ordered_choices) are a hybrid of permutations and combinations.\n  Given are `n` choices, i.e., sets of different values.\n  Each choice could contain any number of different values.\n  Any two choices must either be disjoint or contain the values.\n  Now an element of the space contains one value from each choice and the order matters.\n  So permutations and permutations with repetitions can be represented as ordered choices.\n  - Nullary Operators:\n    - [choose_and_shuffle](https://thomasweise.github.io/moptipy/moptipy.operators.ordered_choices.html#module-moptipy.operators.ordered_choices) picks one random value from each choice and shuffles them.\n- [`n`-dimensional spaces of real numbers](https://thomasweise.github.io/moptipy/moptipy.spaces.html#moptipy.spaces.vectorspace.VectorSpace) are subsets of the `n`-dimensional real numbers.\n  They are `numpy` arrays representing vectors of length `n`.\n  On each dimension, a [lower](https://thomasweise.github.io/moptipy/moptipy.spaces.html#moptipy.spaces.vectorspace.VectorSpace.lower_bound) and an [upper bound](https://thomasweise.github.io/moptipy/moptipy.spaces.html#moptipy.spaces.vectorspace.VectorSpace.upper_bound) are imposed.\n  - Nullary Operators:\n    - [uniform sampling](https://thomasweise.github.io/moptipy/moptipy.operators.vectors.html#module-moptipy.operators.vectors.op0_uniform) samples a point from the uniform distribution resulting from the lower- and upper bound of the search space.\n\n\n### 4.3. Implemented Problems\n\nWithin this package, we implement a subset of basic problems that are useful for testing different aspects of the algorithms and spaces we provide.\n`moptipy` itself is not intended to be a collection of optimization problems, but more a collection of algorithms, spaces, and operators.\nWe do need a set of examples to [unit test](#81-unit-tests-and-static-analysis) these components, so a set of [example problems](https://thomasweise.github.io/moptipy/moptipy.examples.html#module-moptipy.examples) is indeed provided.\nMore examples and practical applications of `moptipy` can be found in the [`moptipyapps`](https://thomasweise.github.io/moptipyapps) package, which is available on [GitHub](https://github.com/thomasWeise/moptipyapps) and in [`PyPi`](https://pypi.org/project/moptipyapps).\nThe `moptipyapps` package is where we will include future examples and benchmark cases, such as for the Quadratic Assignment Problem ([QAP](https://thomasweise.github.io/moptipyapps/#the-quadratic-assignment-problem-qap)), the Traveling Salesperson Problem ([TSP](https://thomasweise.github.io/moptipyapps/#the-traveling-salesperson-problem-tsp)), the Traveling Tournament Problem ([TTP](https://thomasweise.github.io/moptipyapps/#the-traveling-tournament-problem-ttp)), Two-Dimensional [Bin Packing](https://thomasweise.github.io/moptipyapps/#two-dimensional-bin-packing-problem), or [Dynamic Control](https://thomasweise.github.io/moptipyapps/#dynamic-controller-synthesis).\n\n- [Bit Strings](https://thomasweise.github.io/moptipy/moptipy.spaces.html#moptipy.spaces.bitstrings.BitStrings) of a fixed length `n`:\n  - The minimization version of the [1D Ising Model](https://thomasweise.github.io/moptipy/moptipy.examples.bitstrings.html#moptipy.examples.bitstrings.ising1d.Ising1d), where the goal is to ensure that all bits have the same values as their neighbors.\n  - The minimization version of the [Jump](https://thomasweise.github.io/moptipy/moptipy.examples.bitstrings.html#module-moptipy.examples.bitstrings.jump) problem, which is equivalent to OneMax, but has a deceptive region right before the optimum.\n  - The minimization version of the well-known [LeadingOnes](https://thomasweise.github.io/moptipy/moptipy.examples.bitstrings.html#module-moptipy.examples.bitstrings.leadingones) problem, where the goal is to maximize the length of the trailing substring of all `True` bits.\n  - The minimization version of the well-known [OneMax](https://thomasweise.github.io/moptipy/moptipy.examples.bitstrings.html#module-moptipy.examples.bitstrings.onemax) problem, where the goal is to maximize the number of `True` bits in a string.\n  - The minimization version of the [Trap](https://thomasweise.github.io/moptipy/moptipy.examples.bitstrings.html#module-moptipy.examples.bitstrings.trap) problem, which is equivalent of OneMax, but with the optimum and worst-possible solution swapped.\n    This problem is therefore highly deceptive.\n  - The [W-Model](https://thomasweise.github.io/moptipy/moptipy.examples.bitstrings.html#module-moptipy.examples.bitstrings.w_model), a problem that exhibits tunable neutrality, epistasis, ruggedness, and deceptiveness.\n  - The minimization version of the well-known [ZeroMax](https://thomasweise.github.io/moptipy/moptipy.examples.bitstrings.html#module-moptipy.examples.bitstrings.zeromax) problem, which is the exact opposite of OneMax and has the goal to find the bit string of all `False` values.\n- [Permutations](https://thomasweise.github.io/moptipy/moptipy.spaces.html#moptipy.spaces.permutations.Permutations) (with and without Repetitions):\n  - The NP-hard Job Shop Scheduling Problem ([JSSP](https://thomasweise.github.io/moptipy/moptipy.examples.jssp.html#module-moptipy.examples.jssp)), where the goal is to find an assignment of jobs to machines with the minimum makespan.\n    On \u003chttps://thomasweise.github.io/oa_data/\u003e, we provide several zip archives with results obtained with [`moptipy`](https://thomasweise.github.io/moptipy) on the JSSP.\n- [`n`-dimensional spaces of real numbers](https://thomasweise.github.io/moptipy/moptipy.spaces.html#moptipy.spaces.vectorspace.VectorSpace)\n  - [Ackley's Function](https://thomasweise.github.io/moptipy/moptipy.examples.vectors.html#module-moptipy.examples.vectors.ackley)\n  - [Sphere Function](https://thomasweise.github.io/moptipy/moptipy.examples.vectors.html#module-moptipy.examples.vectors.sphere)\n\n\n## 5. Data Formats\n\nWe develop several data formats to store and evaluate the results of computational experiments with our [`moptipy`](https://thomasweise.github.io/moptipy) software.\nHere you can find their basic definitions.\nOn \u003chttps://thomasweise.github.io/oa_data/\u003e, we provide several zip archives with results obtained with our software.\nFor example, you could download the [results](https://thomasweise.github.io/oa_data/jssp/jssp_hcr_swapn.tar.xz) of the [hill climber with restarts](https://thomasweise.github.io/moptipy/moptipy.algorithms.so.html#module-moptipy.algorithms.so.hill_climber_with_restarts) on the Job Shop Scheduling Problem ([JSSP](https://thomasweise.github.io/moptipy/moptipy.examples.jssp.html#module-moptipy.examples.jssp)) using the [operator `swapn`](https://thomasweise.github.io/moptipy/moptipy.operators.permutations.html#module-moptipy.operators.permutations.op1_swapn) that swaps a randomly chosen number of (different) job IDs, for different restart settings.\nThe files and folders in this archive will then exactly comply to the structure discussed here.\n\n\n### 5.1. Log Files\n\nThe philosophy of our log files is:\n\n1. One log file per algorithm run.\n   We always store each run of an algorithm into a single, separate file.\n   This has several advantages:\n   If you execute several runs in parallel, there cannot be any problems when writing the log files.\n   If, instead, we would store multiple runs in a single file, then some synchronization is needed if multiple processes work on the runs for the same setup.\n   Each log file and run is also self-contained.\n   If runs are executed in a distributed fashion, then we can store data about the node where the run is executed in the log file.\n   We can also store the results of the runs right in the log files without requiring any special treatment to identify to which run the results belong (because there only is one run they could belong to).\n   And so on.\n2. Each log file contains all information needed to fully understand the algorithm run, such as\n   1. The results in numerical form, e.g., the [best achieved objective value](#5122-the-section-state).\n   2. The result in textual form, e.g., the textual representation of the [best solution discovered](#5125-the-result-sections)\n      This allows us to later load, use, or validate the result.\n   3. The [random seed](#5123-the-section-setup) used.\n   4. The [termination criteria](#5123-the-section-setup) used, i.e., the maximum objective function evaluations or the maximum runtime or the goal objective value.\n   5. The algorithm name and [configuration](#5123-the-section-setup).\n      This allows us to later understand what we did here and to reproduce the algorithm setup.\n   6. The problem instance name and [parameters](#5123-the-section-setup).\n      This makes sure that we know which problem instance did we solve.\n   7. The [system configuration](#5124-the-section-sys_info), such as the CPU nd operating system and Python version and [library versions](#5124-the-section-sys_info).\n      We need to this to understand and reproduce time-dependent measures or to understand situations where changes in the underlying system configuration may have led to different results.\n   8. [Errors](#5126-the-error-sections), if any occurred.\n      We can guard against errors using [unit tests](#81-unit-tests-and-static-analysis), but it may still happen that a run of the optimization algorithm crashed.\n      Our system tries to catch as detailed error information as possible and store it in the log files in order to allow us to figure out what went wrong.\n   9. The [progress](#5121-the-section-progress) that the algorithm made over time, if capturing this information [was demanded](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.execution.Execution.set_log_improvements).\n   10. The [contents of the archive](#5128-the-archive_j_x-and-archive_j_y-sections) of non-dominated solutions, if we perform [multi-objective optimization](#514-example-log-file-for-multi-objective-optimization).\n   11. The [objective values](#5127-the-archive_qualities-section) of the solutions in the archive of non-dominated solutions, if we perform [multi-objective optimization](#514-example-log-file-for-multi-objective-optimization).\n\nAll of this information is stored (almost) automatically.\nExperiments with [`moptipy`](https://thomasweise.github.io/moptipy) are intended to be self-documenting, such that you can still see what was going on if you open a log file of someone else or one of your log files five years after the experiment.\nEach log file contains all the information, so you will not end up with a situation where you have a \"results file\" but cannot find the matching setup information because it was stored elsewhere.\n\nBy capturing and storing as much information about the setup, configuration, and parameters of each run automatically, we also try to prevent situations where a vital piece of information turns out to be missing some time after the experiment.\nFor example, the library author himself has encountered situations where he still, e.g., had the result of a run but did not store how long it took to get it.\nAnd did no longer remember when this information was needed.\nIn such a case we have no choice but to repeat the experiment.\nUnless our system is clever enough to automatically store such data.\n[`moptipy`](https://thomasweise.github.io/moptipy) is \u0026#x1F601;.\n\n\n#### 5.1.1. File Names and Folder Structure\n\nOne independent run of an algorithm on one problem instance produces one log file.\nEach run is identified by the algorithm that is applied, the problem instance to which it is applied, and the random seed.\nThis tuple is reflected in the file name.\n`rls_swap2_demo_0x5a9363100a272f12.txt`, for example, represents the algorithm `rls_swap2` applied to the problem instance `demo` and started with random seed `0x5a9363100a272f12` (where `0x` stands for hexademical notation).\nThe log files are grouped in a `algorithm`/`instance` folder structure.\nIn the above example, there would be a folder `rls_swap2` containing a folder `demo`, which, in turn, contains all the log files from all runs of that algorithm on this instance.\n\n\n#### 5.1.2. Log File Sections\n\nA log file is a simple text file divided into several sections.\nEach section `X` begins with the line `BEGIN_X` and ends with the line `END_X`.\nThere are three types of sections:\n\n- *[Semicolon-separated values](https://thomasweise.github.io/moptipy/moptipy.utils.html#moptipy.utils.logger.CsvLogSection)* can hold a series of data values, where each row is divided into multiple values and the values are separated by `;`.\n  We use ';', as both '.' and ',' might be misinterpreted as decimal or fractional separaters under different locales, whereas ';', to the best of our knowledge, is rarely used for such purposes.\n  We will still call such sections \"CSV\" sections (comma-separated-values), though, as most people know what CSV is and the structure basically is exactly that, except that we use ';' instead of ','.\n- *[Key-values](https://thomasweise.github.io/moptipy/moptipy.utils.html#moptipy.utils.logger.KeyValueLogSection)* sections represent, well, values for keys in form of a mapping compatible with [YAML](https://yaml.org/spec/1.2.2/#mapping).\n  In other words, each line contains a key, followed by `: `, followed by the value.\n  The keys can be hierarchically structured in scopes, for example `a.b` and `a.c` indicate two keys `b` and `c` that belong to scope `a`.\n  This allows representing complex data such as [configuration parameters](#5123-the-section-setup) in a rather straight-forward, easy-to-parse canonical way.\n  All keys within a section must be unique, i.e., if a section contains a value under key `a.a`, it cannot contain the same key `a.a` again, even with the same value.\n- *[Raw text](https://thomasweise.github.io/moptipy/moptipy.utils.html#moptipy.utils.logger.TextLogSection)* sections contain text without a general or a priori structure, e.g., the string representation of the best solutions found.\n  Obviously, such raw text cannot contain things such as section delimiters or other reserved keywords.\n  Apart from that, basically any type of data may be stored there.\n  This is useful for, for instance, storing the [final solutions](#5125-the-result-sections) of runs or [exceptions caught during the runs](#5126-the-error-sections).\n\nIn all the above sections, the character `#` is removed from output.\nThe character `#` indicates a starting comment and can only be written by the routines dedicated to produce comments.\n\n\n##### 5.1.2.1 The Section `PROGRESS`\n\nWhen setting up an algorithm execution, you can specify whether or not you want to [log the progress](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.execution.Execution.set_log_improvements) of the algorithm.\nIf and only if you choose to log the progress, the `PROGRESS` section will be contained in the log file.\nNotice that this section can be long if the algorithm makes many improvements.\nYou can also choose if you want to [log all algorithm steps](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.execution.Execution.set_log_all_fes) or [only the improving moves](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.execution.Execution.set_log_improvements), the latter being the default behavior.\nIf you really log all algorithm steps, then your log files will contain one line for every objective function evaluation (FE) you perform.\nIt can thus become quite large.\nIn our Job Shop Scheduling example in the [Optimization Algorithms](https://thomasweise.github.io/oa) book, for example, we can do several million FEs within the two minutes of runtime granted to each run.\nThis then would equate to several millions of lines in the `PROGRESS` section of each log file.\nSo normally you would rather only log the improving moves, which would often be between a few ten to a few thousand of lines, which is usually acceptable.\nNotice that even if you do not choose to log the algorithm's progress at all, the [section `STATE`](#5122-the-section-state) with the objective value of the best solution encountered, the FE when it was found, and the consumed runtime, as well as the [`RESULT_*` sections](#5125-the-result-sections) with the best encountered candidate solution and point in the search space, and also the [`SETUP`](#5123-the-section-setup) and [`SYS_INFO`](#5124-the-section-sys_info) still will be included in the log files.\n\nThe `PROGRESS` section contains log points describing the algorithm progress over time in a semicolon-separated values format with one data point per line.\nIt has an internal header describing the data columns.\nThere will at least be the following columns:\n\n1. `fes` denoting the integer number of performed objective value evaluations\n2. `timeMS` the clock time that has passed since the start of the run, measured in milliseconds and stored as integer value.\n   Python actually provides the system clock time in terms of nanoseconds, however, we always round up to the next highest millisecond.\n   We believe that milliseconds are a more reasonable time measure here and a higher resolution is probably not helpful anyway.\n   Due to the upwards-rounding, the lowest possible time at which a log point can occur is at `1` millisecond.\n3. `f` the best-so-far objective value, if only improving moves are logged, or the current objective value, if all moves are logged.\n\nThis configuration is denoted by the header `fes;timeMS;f`.\nAfter this header and until `END_PROGRESS`, each line will contain one data point with values for the specified columns.\n\nIf you perform multi-objective optimization, then one additional column will be added for each objective function.\nThe column header will be `fi` with `i` being the zero-based index of the (`i+1`th) objective function.\n`f` then stands for the [scalarized](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.mo_problem.MOProblem.evaluate) version of the objective values.\n\nYou can copy the contents of this section together with the header into calculation software such as Microsoft Excel or LibreOffice Calc and choose `;` as separator when applying the text-to-column feature.\nThis way, you can directly work on the raw data if you want.\n\nNotice that for each FE, there will be at most one data point but there might be multiple data points per millisecond.\nThis is especially true if we log all FEs.\nUsually, we would log one data point for every improvement of the objective value, though.\n\n\n##### 5.1.2.2 The Section `STATE`\n\nThe end state when the run terminates is logged in the section `STATE` in a [YAML](https://yaml.org/spec/1.2.2/#mapping)-compatible key-value format.\nIt holds at least the following keys:\n\n- `totalFEs` the total number of objective function evaluations performed, as integer\n- `totalTimeMillis` the total number of clock time milliseconds elapsed since the begin of the run, as integer\n- `bestF` the best objective function value encountered during the run\n- `lastImprovementFE` the index of the last objective function evaluation where the objective value improved, as integer\n- `lastImprovementTimeMillis` the time in milliseconds at which the last objective function value improvement was registered, as integer\n\nIn case that multi-objective optimization is performed, please note the following things:\n\n- `bestF` then corresponds to the best [scalarization](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.mo_problem.MOProblem.evaluate) result, i.e., the best value achieved by the scalarization of the objective value vector during the search,\n- `bestFs`, the vector of objective values corresponding to the solution obtaining `bestF`, is also provided (values are semicolon-separated),\n- `archiveSize` is the number of non-dominated solutions collected in the archive, and\n- the values of `lastImprovementFE` and `lastImprovementTimeMillis` may not be reliable anymore:\n  Whenever a solution enters the archive or the best scalarization is improved, this is recorded as improvement.\n  However, since the archive size is always limited and the archive may be pruned due when it reaches its maximum size, it could be that a solution enters the archive which is actually not non-dominated with respect to the whole search but only with respect to the current archive.\n  In other words, `lastImprovementFE` and `lastImprovementTimeMillis` may represent a move that is actually not an absolute improvement. \n\n\n##### 5.1.2.3 The Section `SETUP`\n\nIn this [YAML](https://yaml.org/spec/1.2.2/#mapping)-compatible key-value section, we log information about the configuration of the optimization algorithm as well as the parameters of the problem instance solved.\nThere are at least the following keys:\n\n- [process](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.process.Process) wrapper parameters (scope `p`):\n  - `p.name`: the name of the process wrapper, i.e., a short mnemonic describing its purpose\n  - `p.class`: the python class of the process wrapper\n  - `p.maxTimeMillis`: the maximum clock time in milliseconds, if specified\n  - `p.maxFEs`: the maximum number of objective function evaluations (FEs), if specified\n  - `p.goalF`: the goal objective value, if specified (or computed via the `lower_bound()` of the [objective function](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.objective.Objective))\n  - `p.randSeed`: the random seed (a 64bit unsigned integer) in decimal notation\n  - `p.randSeed(hex)`: the random seed in hexadecimal notation\n  - `p.randGenType`: the class of the random number generator\n  - `p.randBitGenType`: the class of the bit generator used by the random number generator\n  - `p.lowerBound` the lower bound of the (scalarized) objective values that this process can produce (if finite)\n  - `p.upperBound` the upper bound of the (scalarized) objective values that this process can produce (if finite)\n  If multi-objective optimization is performed, the following parameters are added:\n  - `p.archiveMaxSize`: the maximum size of the archive of non-dominated solutions after pruning\n  - `p.archivePruneLimit`: the archive size limit above which pruning will be triggered.\n- [algorithm](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.algorithm.Algorithm) parameters: scope `a`, includes algorithm `name`, `class`, etc.\n- solution [space](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.space.Space) scope `y`, includes `name` and `class` of solution space\n- [objective function](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.objective.Objective) information: scope `f`.\n  If multi-objective optimization is performed, this is the scope of the [multi-objective problem](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.mo_problem.MOProblem).\n  There will be a sub-scope `f.fi` for the `i`th objective function (`i` starts at `0`).\n- search [space](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.space.Space) information (if search space is different from solution space): scope `x`\n- [encoding](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.encoding.Encoding) information (if encoding is defined): scope `g` \n- [archive pruner](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.mo_archive.MOArchivePruner) information (in case of multi-objective optimization): scope `ap`.\n\nIf you implement an own [algorithm](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.algorithm.Algorithm), [objective function](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.objective.Objective), [space](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.space.Space), or your own [search operators](https://thomasweise.github.io/moptipy/moptipy.api.html#module-moptipy.api.operators), then you can overwrite the method [`log_parameters_to(logger)`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.component.Component.log_parameters_to).\nThis method will automatically be invoked when writing the log files of a run.\nIt should *always* start with calling the super implementation (`super().log_parameters_to(logger)`).\nAfter that, you can store key-value pairs describing the parameterization of your component.\nThis way, such information can be preserved in log files.\n\nWe strongly suggest to always do that if you define your own components.\nIt is a very easy way to make sure that your results are reproducible, easy-to-understand, and self-documenting.\n\n\n##### 5.1.2.4 The Section `SYS_INFO`\n\nThe system information section is again a key-value section.\nIt holds key-value pairs describing features of the machine on which the experiment was executed.\nThis includes information about the CPU, the operating system, the Python installation, as well as the version information of packages used by moptipy.\nIf your `moptipy` application uses additional Python libraries, then it is strongly suggested to also include their versions in the log files.\nThis can be done by invoking the function [`add_dependency`](https://thomasweise.github.io/moptipy/moptipy.utils.html#moptipy.utils.sys_info.add_dependency) **before** running any experiment.\nThis way, you can add the name of a library that your application depends on.\nThe system will then automatically get the version information of that library and include it into the log files.\n\n\n##### 5.1.2.5 The `RESULT` Sections\n\nThe textual representation of the best encountered solution (whose objective value is noted as `bestF` in section `STATE`) is stored in the section `RESULT_Y`.\nSince we can use many different solution spaces, this section just contains raw text.\n\nIf the search and solution space are different, the section `RESULT_X` is included.\nIt then holds the point in the search space corresponding to the solution presented in `RESULT_Y`.\n\nBoth sections are plain texts, the results of the [`to_str`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.space.Space.to_str) method of the corresponding [`Space`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.space.Space) instances for the search and solution spaces.\nIt is therefore possible to design suitable text representations for arbitrary solution data structures and have them properly stored in the log files.\n\n\n##### 5.1.2.6 The `ERROR` Sections\n\nOur package has mechanisms to catch and store errors that occurred during the experiments.\nEach type of error will be stored in a separate log section and each such sections may store the class of the error in form `exceptionType: error-class`, the error message in the form `exceptionValue: error-message` and the stack trace line by line after a line header `exceptionStackTrace:`.\nThe following exception sections are currently supported:\n\n- If an exception is encountered during the algorithm run, it will be store in section `ERROR_IN_RUN`.\n- If an exception occurred in the context of the optimization process, it will be stored in `ERROR_IN_CONTEXT`.\n  This may be an error during the execution of the algorithm, or, more likely, an error in the code that accesses the process data afterwards, e.g., that processes the best solution encountered.\n- If the validation of the finally returned candidate solution failed, the resulting error will be stored in section `ERROR_INVALID_Y`.\n- If the internally remembered best objective value does not match to the objective value of the internally remembered best solution after re-evaluating it at the end, the corresponding information will be stored in section `ERROR_BEST_F_MISMATCH`.\n- If the validation of the finally returned point in the search space failed, the resulting error will be stored in section `ERROR_INVALID_X`.\n- If an inconsistency in the time measurement is discovered, this will result in the section `ERROR_TIMING`.\n  Such an error may be caused when the computer clock is adjusted during the run of an optimization algorithm.\n  It will also occur if an algorithm terminates without performing even a single objective function evaluation.\n- In the unlikely case that an exception occurs during the writing of the log but writing can somehow continue, this exception will be stored in section `ERROR_IN_LOG`.\n\n\n##### 5.1.2.7 The `ARCHIVE_QUALITIES` Section\n\nIf multi-objective optimization is performed, the `process` object will automatically collect an archive of non-dominated solutions.\nIn the CSV-formatted section `ARCHIVE_QUALITIES` of the log files, we will find one row per non-dominated solution in the archive.\nThe first number in the row is the scalarized overall solution quality `f`, followed by the value `fi` of the `i`th objective function (`i` starts at `0`).\nThe solutions corresponding to row `j` of this section appear in the `ARCHIVE_j_X` and `ARCHIVE_j_Y` sections (`j` starts at `0`).\n\n\n##### 5.1.2.8 The `ARCHIVE_j_X` and `ARCHIVE_j_Y` Sections\n\nIn multi-objective optimization, the `process` object will automatically collect an archive of non-dominated solutions.\nThe sections `ARCHIVE_j_X` contains the point in the search space and `ARCHIVE_j_Y` the point in the solution space corresponding to the `j`th element of the archive.\nThe sections are plain texts, the results of the [`to_str`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.space.Space.to_str) method of the corresponding [`Space`](https://thomasweise.github.io/moptipy/moptipy.api.html#moptipy.api.space.Space) instances for the search and solution spaces.\n\n\n#### 5.1.3. Example for Single-Objective Optimization\n\nYou can execute the following Python code to obtain an example log file.\nThis code is also available in file [examples/log_file_jssp.py](https://thomasweise.github.io/moptipy/examples/log_file_jssp_py.html):\n\n```python\nfrom moptipy.algorithms.so.rls import RLS  # the algorithm we use\nfrom moptipy.examples.jssp.experiment import run_experiment  # the runner\nfrom moptipy.operators.permutations.op0_shuffle import Op0Shuffle  # 0-ary op\nfrom moptipy.operators.permutations.op1_swap2 import Op1Swap2  # 1-ary op\nfrom pycommons.io.temp import temp_dir  # temp directory tool\n\n# We work in a temporary directory, i.e., delete all generated files on exit.\n# For a real experiment, you would put an existing directory path in `td`\n# by doing `from pycommons.io.path import Path; td = directory_path(\"mydir\")`\n# and not use the `with` block.\nwith temp_dir() as td:  # create temp directory\n    # Execute an experiment consisting of exactly one run.\n    # As example domain, we use the job shop scheduling problem (JSSP).\n    run_experiment(\n        base_dir=td,  # working directory = temporary directory\n        algorithms=[  # the set of algorithms to use: we use only 1\n            # an algorithm is created via a lambda\n            lambda _, pwr: RLS(Op0Shuffle(pwr), Op1Swap2())],\n        instances=(\"demo\",),  # use the demo JSSP instance\n        n_runs=1)  # perform exactly one run\n    # The random seed is automatically generated based on the instance name.\n    print(td.resolve_inside(  # so we know algorithm, instance, and seed\n        \"rls_swap2/demo/rls_swap2_demo_0x5a9363100a272f12.txt\")\n          .read_all_str())  # read file into string (which then gets printed)\n# When leaving \"while\", the temp directory will be deleted\n```\n\nThe example log file printed by the above code will then look something like this:\n\n```text\nBEGIN_PROGRESS\nfes;timeMS;f\n1;1;267\n5;1;235\n10;1;230\n20;1;227\n25;1;205\n40;1;200\n84;2;180\nEND_PROGRESS\nBEGIN_STATE\ntotalFEs: 84\ntotalTimeMillis: 2\nbestF: 180\nlastImprovementFE: 84\nlastImprovementTimeMillis: 2\nEND_STATE\nBEGIN_SETUP\np.name: LoggingProcessWithSearchSpace\np.class: moptipy.api._process_ss_log._ProcessSSLog\np.maxTimeMillis: 120000\np.goalF: 180\np.randSeed: 6526669205530947346\np.randSeed(hex): 0x5a9363100a272f12\np.randGenType: numpy.random._generator.Generator\np.randBitGenType: numpy.random._pcg64.PCG64\na.name: rls_swap2\na.class: moptipy.algorithms.rls.RLS\na.op0.name: shuffle\na.op0.class: moptipy.operators.permutations.op0_shuffle.Op0Shuffle\na.op1.name: swap2\na.op1.class: moptipy.operators.permutations.op1_swap2.Op1Swap2\ny.name: gantt_demo\ny.class: moptipy.examples.jssp.gantt_space.GanttSpace\ny.shape: (5, 4, 3)\ny.dtype: h\ny.inst.name: demo\ny.inst.class: moptipy.examples.jssp.instance.Instance\ny.inst.machines: 5\ny.inst.jobs: 4\ny.inst.makespanLowerBound: 180\ny.inst.makespanUpperBound: 482\ny.inst.dtype: b\nf.name: makespan\nf.class: moptipy.examples.jssp.makespan.Makespan\nx.name: perm4w5r\nx.class: moptipy.spaces.permutations.Permutations\nx.nvars: 20\nx.dtype: b\nx.min: 0","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthomasweise%2Fmoptipy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthomasweise%2Fmoptipy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthomasweise%2Fmoptipy/lists"}