pymoo: Multi-objective Optimization in Python

Python has become the programming language of choice for research and industry projects related to data science, machine learning, and deep learning. Since optimization is an inherent part of these research fields, more optimization related frameworks have arisen in the past few years. Only a few of them support optimization of multiple conflicting objectives at a time, but do not provide comprehensive tools for a complete multi-objective optimization task. To address this issue, we have developed pymoo, a multi-objective optimization framework in Python. We provide a guide to getting started with our framework by demonstrating the implementation of an exemplary constrained multi-objective optimization scenario. Moreover, we give a high-level overview of the architecture of pymoo to show its capabilities followed by an explanation of each module and its corresponding sub-modules. The implementations in our framework are customizable and algorithms can be modified/extended by supplying custom operators. Moreover, a variety of single, multi and many-objective test problems are provided and gradients can be retrieved by automatic differentiation out of the box. Also, pymoo addresses practical needs, such as the parallelization of function evaluations, methods to visualize low and high-dimensional spaces, and tools for multi-criteria decision making. For more information about pymoo, readers are encouraged to visit: https://pymoo.org


Introduction
Optimization plays an essential role in many scientific areas, such as engineering, data analytics, and deep learning. These fields are fast-growing and their concepts are employed for various purposes, for instance gaining insights from a large data sets or fitting accurate prediction models. Whenever an algorithm has to handle a significantly large amount of data, an efficient implementation in a suitable programming language is important. Python [41] has become the programming language of choice for the above mentioned research areas over the last few years, not only because it is easy to use but also good community support exists. Python is a high-level, cross-platform, and interpreted programming language that focuses on code readability. A large number of high-quality libraries are available and support for any kind of scientific computation is ensured. These characteristics make Python an appropriate tool for many research and industry projects where the investigations can be rather complex. A fundamental principle of research is to ensure reproducibility of studies and to provide access to materials used in the research, whenever possible. In computer science, this translates to a sketch of an algorithm and the implementation itself. However, the implementation of optimization algorithms can be challenging and specifically benchmarking is time-consuming. Having access to either a good collection of different source codes or a comprehensive library is time-saving and avoids an error-prone implementation from scratch.
To address this need for multi-objective optimization in Python, we introduce pymoo. The goal of our framework is not only to provide state of the art optimization algorithms, but also to cover different aspects related to the optimization blankjul@msu.edu (J. Blank); kdeb@msu.edu (K. Deb) ORCID(s): 0000-0002-2227-6476 (J. Blank); 0000-0001-7402-9939 (K. Deb) process itself. We have implemented single, multi and manyobjective test problems which can be used as a testbed for algorithms. In addition to the objective and constraint values of test problems, gradient information can be retrieved through automatic differentiation [5]. Moreover, a parallelized evaluation of solutions can be implemented through vectorized computations, multi-threaded execution, and distributed computing. Further, pymoo provides implementations of performance indicators to measure the quality of results obtained by a multi-objective optimization algorithm. Tools for an explorative analysis through visualization of lower and higher-dimensional data are available and multicriteria decision making methods guide the selection of a single solution from a solution set based on preferences.
Our framework is designed to be extendable through of its modular implementation. For instance, a genetic algorithm is assembled in a plug-and-play manner by making use of specific sub-modules, such as initial sampling, mating selection, crossover, mutation and survival selection. Each sub-module takes care of an aspect independently and, therefore, variants of algorithms can be initiated by passing different combinations of sub-modules. This concept allows end-users to incorporate domain knowledge through custom implementations. For example, in an evolutionary algorithm a biased initial sampling module created with the knowledge of domain experts can guide the initial search.
Furthermore, we like to mention that our framework is well-documented with a large number of available code-snippets. We created a starter's guide for users to become familiar with our framework and to demonstrate its capabilities. As an example, it shows the optimization results of a bi-objective optimization problem with two constraints. An extract from the guide will be presented in this paper. Moreover, we provide an explanation of each algorithm and source code to run it on a suitable optimization problem in our software documentation. Additionally, we show a definition of test problems and provide a plot of their fitness landscapes. The framework documentation is built using Sphinx [30] and correctness of modules is ensured by automatic unit testing [36]. Most algorithms have been developed in collaboration with the second author and have been benchmarked extensively against the original implementations.
In the remainder of this paper, we first present related existing optimization frameworks in Python and in other programming languages. Then, we provide a guide to getting started with pymoo in Section 3 which covers the most important steps of our proposed framework. In Section 4 we illustrate the framework architecture and the corresponding modules, such as problems, algorithms and related analytics. Each of the modules is then discussed separately in Sections 5 to 7. Finally, concluding remarks are presented in Section 8.

Related Works
In the last decades, various optimization frameworks in diverse programming languages were developed. However, some of them only partially cover multi-objective optimization. In general, the choice of a suitable framework for an optimization task is a multi-objective problem itself. Moreover, some criteria are rather subjective, for instance, the usability and extendibility of a framework and, therefore, the assessment regarding criteria as well as the decision making process differ from user to user. For example, one might have decided on a programming language first, either because of personal preference or a project constraint, and then search for a suitable framework. One might give more importance to the overall features of a framework, for example parallelization or visualization, over the programming language itself. An overview of some existing multi-objective optimization frameworks in Python is listed in Table 1, each of which is described in the following.
Recently, the well-known multi-objective optimization framework jMetal [15] developed in Java [19] has been ported to a Python version, namely jMetalPy [2]. The authors aim to further extend it and to make use of the full feature set of Python, for instance, data analysis and data visualization. In addition to traditional optimization algorithms, jMetalPy also offers methods for dynamic optimization. Moreover, the post analysis of performance metrics of an experiment with several independent runs is automated.
Parallel Global Multiobjective Optimizer, PyGMO [25], is an optimization library for the easy distribution of massive optimization tasks over multiple CPUs. It uses the generalized island-model paradigm for the coarse grained parallelization of optimization algorithms and, therefore, allows users to develop asynchronous and distributed algorithms.
Platypus [21] is a multi-objective optimization framework that offers implementations of state-of-the art algorithms. It enables users to create an experiment with various algorithms and provides post-analysis methods based on metrics and visualization.
A Distributed Evolutionary Algorithms in Python (DEAP) [16] is novel evolutionary computation framework for rapid prototyping and testing of ideas. Even though, DEAP does not focus on multi-objective optimization, however, due to the modularity and extendibility of the framework multi-objective algorithms can be developed. Moreover, parallelization and load-balancing tasks are supported out of the box. Inspyred [18] is a framework for creating bio-inspired computational intelligence algorithms in Python which is not focused on multi-objective algorithms directly, but on evolutionary computation in general. However, an example for NSGA-II [12] is provided and other multi-objective algorithms can be implemented through the modular implementation of the framework.
If the search for frameworks is not limited to Python, other popular frameworks should be considered: PlatEMO [45] in Matlab, MOEA [20] and jMetal [15] in Java, jMetalCpp [31] and PaGMO [3] in C++. Of course this is not an exhaustive list and readers may search for other available options.

Getting Started 1
In the following, we provide a starter's guide for pymoo. It covers the most important steps in an optimization scenario starting with the installation of the framework, defining an optimization problem, and the optimization procedure itself.

Installation
Our framework pymoo is available on PyPI [17] which is a central repository to make Python software package easily accessible. The framework can be installed by using the package manager: $ pip install -U pymoo Some components are available in Python and additionally in Cython [1]. Cython allows developers to annotate existing Python code which is translated to C or C++ programming languages. The translated files are compiled to a binary executable and can be used to speed up computations.
During the installation of pymoo, attempts are made for compilation, however, if unsuccessful due to the lack of a suitable compiler or other reasons, the pure Python version is installed. We would like to emphasize that the compilation is optional and all features are available without it. More detail about the compilation and troubleshooting can be found in our installation guide online.

Problem Definition
In general, multi-objective optimization has several objective functions with subject to inequality and equality constraints to optimize [11]. The goal is to find a set of solutions (variable vectors) that satisfy all constraints and are as good as possible regarding all its objectives values. The problem definition in its general form is given by: The formulation above defines a multi-objective optimization problem with variables, objectives, inequality, and equality constraints. Moreover, for each variable , lower and upper variable boundaries ( and ) are also defined.
In the following, we illustrate a bi-objective optimization problem with two constraints.
It consists of two objectives ( = 2) where 1 ( ) is minimized and 2 ( ) maximized. The optimization is with subject to two inequality constraints ( = 2) where 1 ( ) is formulated as a less-than-equal-to and 2 ( ) as a greaterthan-equal-to constraint. The problem is defined with respect to two variables ( = 2), 1 and 2 , which both are in the range [−2, 2]. The problem does not contain any equality constraints ( = 0). Contour plots of the objective functions are shown in Figure 1. The contours of the objective function 1 ( ) are represented by solid lines and 2 ( ) by dashed lines. Constraints 1 ( ) and 2 ( ) are parabolas which intersect the 1 -axis at (0.1, 0.9) and (0.4, 0.6). The Paretooptimal set is marked by a thick orange line. Through the combination of both constraints the Pareto-set is split into two parts. Analytically, the Pareto-optimal set is given by   In the following, we provide an example implementation of the problem formulation above using pymoo. We assume the reader is familiar with Python and has a fundamental knowledge of NumPy [35] which is utilized to deal with vector and matrix computations.
In pymoo, we consider pure minimization problems for optimization in all our modules. However, without loss of generality an objective which is supposed to be maximized, can be multiplied by −1 and be minimized [8]. Therefore, we minimize − 2 ( ) instead of maximizing 2 ( ) in our optimization problem. Furthermore, all constraint functions need to be formulated as a less-than-equal-to constraint. For this reason, 2 ( ) needs to be multiplied by −1 to flip the ≥ to a ≤ relation. We recommend the normalization of constraints to give equal importance to each of them. For 1 ( ), the constant 'resource' value of the constraint is 2 ⋅ (−0.1) ⋅ (−0.9) = 0.18 and for 2 ( ) it is 20 ⋅ (−0.4) ⋅ (−0.6) = 4.8, respectively. We achieve normalization of constraints by dividing 1 ( ) and 2 ( ) by the corresponding constant [9].
Finally, the optimization problem to be optimized using pymoo is defined by: Next, the derived problem formulation is implemented in Python. Each optimization problem in pymoo has to inherit from the Problem class. First, by calling the super() function the problem properties such as the number of variables (n_var), objectives (n_obj) and constraints (n_constr) are initialized. Furthermore, lower (xl) and upper variables boundaries (xu) are supplied as a NumPy array. Additionally, the evaluation function _evaluate needs to be overwritten from the superclass. The method takes a two-dimensional NumPy array x with rows and columns as an input. Each row represents an individual and each column an optimization variable. After doing the necessary calculations, the objective values are added to the dictionary out with the key F and the constraints with key G.
As mentioned above, pymoo utilizes NumPy [35] for most of its computations. To be able to retrieve gradients through automatic differentiation we are using a wrapper around NumPy called Autograd [32]. Note that this is not obligatory for a problem definition.

Algorithm Initialization
Next, we need to initialize a method to optimize the problem. In pymoo, an algorithm object needs to be created for optimization. For each of the algorithms an API documentation is available and through supplying different parameters, algorithms can be customized in a plug-and-play manner. In general, the choice of a suitable algorithm for optimization problems is a challenge itself. Whenever problem characteristics are known beforehand we recommended using those through customized operators. However, in our case the optimization problem is rather simple, but the aspect of having two objectives and two constraints should be considered. For this reason, we decided to use NSGA-II [12] with its default configuration with minor modifications. We chose a population size of 40, but instead of generating the same number of offsprings, we create only 10 each generation. This is a steady-state variant of NSGA-II and it is likely to improve the convergence property for rather simple optimization problems without much difficulties, such as the existence of local Pareto-fronts. Moreover, we enable a duplicate check which makes sure that the mating produces offsprings which are different with respect to themselves and also from the existing population regarding their variable vectors. To illustrate the customization aspect, we listed the other unmodified default operators in the code-snippet below. The constructor of NSGA2 is called with the supplied parameters and returns an initialized algorithm object.

Optimization
Next, we use the initialized algorithm object to optimize the defined problem. Therefore, the minimize function with both instances problem and algorithm as parameters is called. Moreover, we supply the termination criterion of running the algorithm for 40 generations which will result in 40 + 40 × 10 = 440 function evaluations. In addition, we define a random seed to ensure reproducibility and enable the verbose flag to see printouts for each generation. The method returns a Result object which contains the non-dominated set of solutions found by the algorithm. The optimization results are illustrated in Figure 2 where the design space is shown in Figure 2a and in the objective space in Figure 2b. The solid line represents the analytically derived Pareto set and front in the corresponding space and the circles solutions found by the algorithm. It can be observed that the algorithm was able to converge and a set of nearly-optimal solutions was obtained. Some additional post-processing steps and more details about other aspects of the optimization procedure can be found in the remainder of this paper and in our software documentation.
The starters guide showed the steps starting from the installation up to solving an optimization problem. The investigation of a constrained bi-objective problem demonstrated the basic procedure in an optimization scenario.

Architecture
Software architecture is fundamentally important to keep source code organized. On the one hand, it helps developers and users to get an overview of existing classes, and on the other hand, it allows flexibility and extendibility by adding new modules. Figure 3 visualizes the architecture of pymoo. The first level of abstraction consists of the optimization problems, algorithms and analytics. Each of the modules can be categorized into more detail and consists of multiple sub-modules.  (i) Problems: Optimization problems in our framework are categorized into single, multi, and many-objective test problems. Gradients are available through automatic differentiation and parallelization can be implemented by using a variety of techniques.
(ii) Optimization: Since most of the algorithms are based on evolutionary computations, operators such as sampling, mating selection, crossover and mutation have to be chosen or implemented. Furthermore, because many problems in practice have one or more constraints, a methodology for handling those must be incorporated. Some algorithms are based on decomposition which splits the multi-objective problem into many single-objective problems. Moreover, when the algorithm is used to solve the problem, a termination criterion must be defined either explicitly or implicitly by the implementation of the algorithm.
(iii) Analytics: During and after an optimization run analytics support the understanding of data. First, intuitively the design space, objective space, or other metrics can be explored through visualization. Moreover, to measure the convergence and/or diversity of a Pareto-optimal set performance indicators can be used.
To support the decision making process either through finding points close to the area of interest in the objective space or high trade-off solutions. This can be applied either during an optimization run to mimic interactive optimization or as a post analysis.
In the remainder of the paper, we will discuss each of the modules mentioned in more detail.

Problems
It is common practice for researchers to evaluate the performance of algorithms on a variety of test problems. Since we know no single-best algorithm for all arbitrary optimization problems exist [51], this helps to identify problem classes where the algorithm is suitable. Therefore, a collection of test problems with different numbers of variables, objectives or constraints and alternating complexity becomes handy for algorithm development. Moreover, in a multi-objective context, test problems with different Pareto-front shapes or varying variable density close to the optimal region are of interest.

Implementations
In our framework, we categorize test problems regarding the number of objectives: single-objective (1 objective), multi-objective (2 or 3 objectives) and many-objective (more than 3 objectives). Test problems implemented in pymoo are listed in Table 2. For each problem the number of variables, objectives, and constraints are indicated. If the test problem is scalable to any of the paramaters, we label the problem with (s). If the problem is scalable, but a default number was original proposed we indicate that with surrounding brackets. In case the category does not apply, for example because we refer to a test problem family with several functions, we use (⋅).
The implementations in pymoo let end-users define what values of the corresponding problem should be returned. On an implementation level, the evaluate function of a Problem instance takes a list return_value_of which contains the type of values being returned. By default the objective values "F" and if the problem has constraints the constraint violation "CV" are included. The constraint function values can be returned independently by adding "G". This gives developers the flexibility to receive the values that are needed for their methods.

Gradients
All our test problems are implemented using Autograd [32]. Therefore, automatic differentiation is supported out of the box. We have shown in Section 3 how a new optimization problem is defined.
If gradients are desired to be calculated the prefix "d" needs to be added to the corresponding value of the return_value_of list. For instance to ask for the objective values and its gradients return_value_of = ["F", "dF"].
Let us consider the problem we have implemented shown in Equation 3. The derivation of the objective functions with respect to each variable is given by: The  It can easily be verified that the values are matching with the analytic gradient derivation. The gradients for the constraint functions can be calculated accordingly by adding "dG" to the return_value_of list.

Parallelization
If evaluation functions are computationally expensive, a serialized evaluation of a set of solutions can become the bottleneck of the overall optimization procedure. For this reason, parallelization is desired for an use of existing computational resources more efficiently and distribute long-running calculations. In pymoo, the evaluation function receives a set of solutions if the algorithm is utilizing a population. This empowers the user to implement any kind of parallelization as long as the objective values for all solutions are written as an output when the evaluation function terminates. In our framework, a couple of possibilities to implement parallelization exist: (i) Vectorized Evaluation: A common technique to parallelize evaluations is to use matrices where each row represents a solution. Therefore, a vectorized evaluation refers to a column which includes the variables of all solutions. By using vectors the objective values of all solutions are calculated at once. The codesnippet of the example problem in Section 3 shows such an implementation using NumPy [35]. To run calculations on a GPU, implementing support for Py-Torch [37] tensors can be done with little overhead given suitable hardware and correctly installed drivers.
(ii) Threaded Loop-wise Evaluation: If the function evaluation should occur independently, a for loop can be used to set the values. By default the evaluation is serialized and no calculations occur in parallel. By providing a keyword to the evaluation function, pymoo spawns a thread for each evaluation and manages those by using the default thread pool implementation in Python. This behaviour can be implemented out of the box and the number of parallel threads can be modified.
(iii) Distributed Evaluation: If the evaluation should not be limited to a single machine, the evaluation itself can be distributed to several workers or a whole cluster. We recommend using Dask [7] which enables distributed computations on different levels. For instance, the matrix operation itself can be distributed or a whole function can be outsourced. Similar to the loop wise evaluation each individual can be evaluate element-wise by sending it to a worker.

Optimization Module
The optimization module provides different kinds of submodules to be used in algorithms. Some of them are more of a generic nature, such as decomposition and termination criterion, and others are more related to evolutionary computing. By assembling those modules together algorithms are built.

Algorithms
Available algorithm implementations in pymoo are listed in Table 3. Compared to other optimization frameworks the list of algorithms may look rather short, however, each algorithm is customizable and variants can be initialized with different parameters. For instance, a Steady-State NSGA-II [34] can be initialized by setting the number of offspring  Table 3 Multi-objective Optimization Algorithms.

Operators
The following evolutionary operators are available: (i) Sampling: The initial population is mostly based on sampling. In some cases it is created through domain knowledge and/or some solutions are already evaluated, they can directly be used as an initial population. Otherwise, it can be sampled randomly for real, integer, or binary variables. Additionally, Latin-Hypercube Sampling [33] can be used for real variables.
(ii) Crossover: A variety of crossover operators for different type of variables are implemented. In Figure 4 some of them are presented. Figures 4a-4d help to visualize the information exchange in a crossover with two parents being involved. Each row represents an offspring and each column a variable. The corresponding boxes indicate whether the values of the offspring are inherited from the first or from the second parent. For one and two point crossovers it can be observed that either one or two cuts in the variable sequence exist. Contrarily, the Uniform Crossover (UX) does not have any clear pattern, because each variable is chosen randomly either from the first or from the second parent. For the Half Uniform Crossover (HUX) half of the variables, which are different, are exchanged. For the purpose of illustration, we have created two parents that have different values in 10 different positions. For real variables, Simulated Binary Crossover [13] is known to be an efficient crossover. It mimics the crossover of binary encoded variables. In Figure 4e the probability distribution when the parents 1 = 0.2 and 2 = 0.8 where ∈ [0, 1] with = 0.8 are recombined is shown. Analogously, in case of integer variables we subtract 0.5 from the lower and add 0.5 − to the upper bound before applying the crossover and round to the nearest integer afterwards (see Figure 4f).

(iii) Mutation: For real and integer variables Polynomial
Mutation [13] and for binary variables Bitflip mutation is provided.
Different problems require different type of operators. In practice, if a problem is supposed to be solved repeatedly, it makes sense to customize the evolutionary operators to improve the convergence of the algorithm. Moreover, for custom variable types, for instance trees or mixed variables, custom operators can be implemented easily and called by algorithm class. Our software documentation contains examples for custom modules, operators and variable types.

Termination Criterion
For every algorithm it must be determined when it should terminate a run. This can be simply based on a predefined number of function evaluations, iterations, or more advanced criteria such as the change of a performance metric over time. For example, we have implemented a termination criterion based on the design space difference between generations. To make the termination criterion more robust the last generations are considered. The largest movement from a solution to its closest neighbour is tracked across generation and whenever it is below a certain threshold the algorithm is considered to have converged. Analogously, the movement in the objective space can be chosen for termination in pymoo.

Decomposition
Decomposition transforms multi-objective problems into many single-objective optimization problems [42]. Such a technique can be either embedded in a multi-objective algorithm and solved simultaneously or independently using a single-objective optimizer. Some decomposition methods are based on the lp-metrics with different values. For instance, a naive but frequently used decomposition approach is the Weighted-Sum Method ( = 1), which is known to be not able to converge to the non-convex part of a Paretofront [11]. Moreover, instead of summing values, Tchebysheff Method ( = ∞) considers only the maximum value of the difference between the ideal point and a solution. Similarly, the Achievement Scalarization Function (ASF) [49] and a modified version Augmented Achievement Scalarization Function (AASF) [50] use the maximum of all differences. Furthermore, Penalty Boundary Intersection (PBI) [52] is calculated by a weighted sum of the norm of the projection of a point onto the reference direction and the perpendicular distance. Also it is worth to note that normalization is essential for any kind of decomposition. All decomposition techniques mentioned above are implemented in pymoo.

Performance Indicators
For single-objective optimization algorithms the comparison regarding performance is rather simple because each optimization run results in a single best solution. In multiobjective optimization, however, each run returns a non-dominated set of solutions. To compare sets of solutions various performance indicators have been proposed in the past [29]. In pymoo most commonly used performance indicators are described: (i) GD/IGD: Given the Pareto-front PF the deviation between the non-dominated set S found by the algorithm and the optimum can be measured. Following this principle, Generational Distance (GD) Indicator [46] calculates the average euclidean distance in the objective space from each solution in S to the closest solution in PF. This measures the convergence of S, but does not indicate whether a good diversity on the Pareto-front has been reached. Similarly, Inverted Generational Distance (IGD) Indicator [46] measures the average Euclidean distance in the objective space from each solution in PF to the closest solution in S. The Pareto-front as a whole needs to be covered by solutions from S to minimize the performance metric. However, IGD is known to be not Pareto compliant [24].
(ii) GD+/IGD+: A variation of GD and IGD has been proposed in [24]. The Euclidean distance is replaced by a distance measure that takes the dominance relation into account. The authors show that IGD+ is weakly Pareto compliant.
(iii) Hypervolume: Moreover, the dominated portion of the objective space can be used to measure the quality of non-dominated solutions [54]. Instead of the Pareto-front a reference point needs to be provided. It has been shown that Hypervolume is Pareto compliant [53]. Because the performance metric becomes computationally expensive in higher dimensional spaces the exact measure becomes intractable. However, we plan to include some proposed approximation methods in the near future.
Performance indicators are used to compare existing algorithms. Moreover, the development of new algorithms can be driven by the goodness of different metrics itself.

Visualization
The visualization of intermediate steps or the final result is inevitable. In multi and many-objective optimization visualization of the objective space is of interest especially, and focuses on visualizing trade-offs between solutions. Depending on the dimensionality different types of plots are suitable to represent a single or a set of solutions. In pymoo the implemented visualizations wrap around the well-known plotting library in Python Matplotlib [23]. Keyword arguments provided by Matplotlib itself are still available which allows to modify for instance the color, thickness, opacity of lines, points or other shapes. Therefore, all visualization techniques are customizable and extendable.
For 2 or 3 objectives, scatter plots (see Figure 5a and 5b) can give a good intuition about the solution set. Tradeoffs can be observed by considering the distance between two points. It might be desired to normalize each objective to make sure a comparison between values is based on relative and not absolute values. Pairwise Scatter Plots (see Figure 5c) visualize more than 3 objectives by showing each pair of axes independently. The diagonal is used to label the corresponding objectives.
Also, high-dimensional data can be illustrated by Parallel Coordinate Plots (PCP) as shown in Figure 5d. All axes are plotted vertically and represent an objective. Each solution is illustrated by a line from the left to the right. The intersection of a line and an axis indicate the value of the solution regarding the corresponding objective. For the purpose of comparison solution(s) can be highlighted by varying color and opacity.
Moreover, a common practice is to project the higher dimensional objective values onto the 2D plane using a transformation function. Radviz (Figure 5e) visualizes all points in a circle and the objective axes are uniformly positioned around on the perimeter. Considering a minimization problem and a non-dominated set of solutions, an extreme point very close to an axis represents the worst solution for that corresponding objective, but is comparably "good" in one or many other objectives. Similarly, Star Coordinate Plots (Figure 5f) illustrate the objective space, except that the transformation function allows solutions outside of the circle.
Heatmaps (Figure 5g) are used to represent the goodness of solutions through colors. Each row represents a solution and each column a variable. We leave the choice to the enduser of what color map to use and whether light or dark colors illustrate better or worse solutions. Also, solutions can be sorted lexicographically by their corresponding objective values.
Instead of visualizing a set of solutions, one solution can be illustrated at a time. The Petal Diagram (Figure 5h) is a pie diagram where the objective value is represented by each piece's diameter. Colors are used to further distinguish the pieces. Finally, the Spider-Web or Radar Diagram (Figure 5i) shows the objectives values as a point on an axis. The ideal and nadir point [11] is represented by the inner and outer polygon. By definition the solution lies in between those two extremes. If the objective space ranges are scaled differently, normalization for the purpose of plotting can be enabled and the diagram becomes symmetric.

Decision Making
In practice, after obtaining a set of non-dominated solutions a single solution has to be chosen for implementation.
(i) Compromise Programming: One way of making a decision is to compute value of a scalarized and aggregated function and select one solution based on minimum or maximum value of the function. In pymoo a number of scalarization functions described in Section 6.4 can be used to come to a decision regarding desired weights of objectives.
(ii) Pseudo-Weights: However, a more intuitive way to chose a solution out of a Pareto-front is the pseudoweight vector approach proposed in [11]. The pseudo weight for the -th objective function is calculated by: . (5) The normalized distance to the worst solution regarding each objective is calculated. It is interesting to note that for non-convex Pareto-fronts, the pseudo weight does not correspond to the result of an optimization using the weighted sum method.
(iii) High Trade-Off Solutions: Furthermore, high tradeoff solutions are usually of interest, but not straightforward to detect in higher-dimensional objective spaces. We have implemented the procedure proposed in [40]. It was described to be embedded in an algorithm to guide the search; we, however, use it for post-processing. The metric for each solution pair and in a nondominated set is given by: where the numerator represents the aggregated sacrifice and the denominator the aggregated gain. The trade-off measure ( , ) for each solution with respect to a set of solutions is obtained by: It finds the minimum ( , ) from to all other solutions ∈ . Instead of calculating the metric with respect to all others, we provide the option to only consider the closest neighbors in the objective space to reduce the computational complexity.
Multi-objective frameworks should include methods for multi-criteria decision making and support end-user further in choosing a solution out of a trade-off solution set.

Concluding Remarks
This paper has introduced pymoo, a multi-objective optimization framework in Python. We have walked through our framework beginning with the installation up to the optimization of a constrained bi-objective optimization problem. Moreover, we have presented the overall architecture of the framework consisting of three core modules: Problems, Optimization, and Analytics. Each module has been described in depth and illustrative examples have been provided. We have shown that our framework covers various aspects of multi-objective optimization including the visualization of high-dimensional spaces and multi-criteria decision making to finally select a solution out of the obtained solution set. One distinguishing feature of our framework with other existing ones is that we have provided a few options for various key aspects of a multi-objective optimization task, providing standard evolutionary operators for optimization, standard performance metrics for evaluating a run, standard visualization techniques for showcasing obtained trade-off solutions, and a few approaches for decision-making. Most such implementations were originally suggested and developed by the second author and his collaborators for more than 25 years. Hence, we consider that the implementations of all such ideas are authentic and error-free. Thus, the results from the proposed framework should stay as benchmark results of implemented procedures.
However, the framework can be extended to make it more extensive. In the future, we plan to implement a more optimization algorithms and test problems to provide more choices to end-users. Also, we aim to implement some methods from the classical literature on single-objective optimization which can also be used for multi-objective optimization through decomposition or embedded as a local search. So far, we have provided a few basic performance metrics. We plan to extend this by creating a module that runs a list of algorithms on test problems automatically and provides a statistics of different performance indicators.
Furthermore, we like to mention that any kind of contribution is more than welcome. We see our framework as a collaborative collection from and to the multi-objective optimization community. By adding a method or algorithm to pymoo the community can benefit from a growing comprehensive framework and it can help researchers to advertise their methods. In general, different kinds of contributions are possible and more information can be found online. Moreover, we would like to mention that even though we try to keep our framework as bug-free as possible, in case of exceptions during the execution or doubt of correctness, please contact us directly or use our issue tracker.