From f0aab8312eeda54a91eb7af73ea6b63e98ac00c1 Mon Sep 17 00:00:00 2001 From: Julian Blank Date: Mon, 21 Oct 2019 11:47:15 -0400 Subject: [PATCH] VERSION 0.3.2 --- README.rst | 2 +- benchmark/benchmark_nelder_mead.py | 11 +- benchmark/benchmark_nsga2.py | 7 +- benchmark/benchmark_nsga3.py | 6 +- pymoo/algorithms/moead.py | 8 -- pymoo/algorithms/nsga2.py | 8 -- pymoo/algorithms/nsga3.py | 8 -- pymoo/algorithms/rnsga2.py | 9 -- pymoo/algorithms/rnsga3.py | 9 -- pymoo/algorithms/so_adam.py | 48 +++++++++ pymoo/algorithms/so_de.py | 10 +- pymoo/algorithms/so_genetic_algorithm.py | 12 +-- pymoo/algorithms/so_gradient_descent.py | 89 ++++++++++++++++ pymoo/algorithms/so_nelder_mead.py | 51 ++++++--- pymoo/algorithms/unsga3.py | 8 -- pymoo/factory.py | 38 +++---- pymoo/model/algorithm.py | 18 +++- pymoo/model/plot.py | 3 +- pymoo/model/population.py | 2 +- pymoo/model/problem.py | 35 ++++-- pymoo/model/termination.py | 62 +++++++++++ .../crossover/simulated_binary_crossover.py | 3 + .../selection/tournament_selection.py | 2 +- pymoo/performance_indicator/kktpm.py | 2 +- pymoo/problems/single/knapsack.py | 7 +- pymoo/usage/algorithms/usage_custom_output.py | 7 +- pymoo/usage/algorithms/usage_de.py | 4 +- pymoo/usage/algorithms/usage_ga.py | 1 + pymoo/usage/algorithms/usage_ga_custom.py | 100 ++++++++++++++++++ pymoo/usage/algorithms/usage_ga_equality.py | 54 ++++++++++ pymoo/usage/algorithms/usage_moead.py | 3 +- pymoo/usage/algorithms/usage_nsga2.py | 2 +- pymoo/usage/algorithms/usage_nsga2_binary.py | 2 +- pymoo/usage/algorithms/usage_nsga2_custom.py | 34 ++++-- pymoo/usage/algorithms/usage_rnsga3.py | 4 +- pymoo/usage/algorithms/usage_so_adam.py | 20 ++++ .../algorithms/usage_so_gradient_descent.py | 19 ++++ ...nelder_mead.py => usage_so_nelder_mead.py} | 6 +- pymoo/usage/usage_repair.py | 2 +- pymoo/util/display.py | 9 +- pymoo/util/reference_direction.py | 8 +- pymoo/vendor/global_opt.py | 3 +- .../go_benchmark_functions/go_funcs_G.py | 3 + .../go_benchmark_functions/go_funcs_L.py | 2 +- pymoo/version.py | 2 +- tests/algorithms/test_algorithms.py | 30 +++++- tests/algorithms/test_nelder_mead.py | 12 +-- tests/operators/test_crossover.py | 8 +- tests/scratch/__init__.py | 0 tests/scratch/scratch1.py | 27 +++++ tests/scratch/scratch2.py | 17 +++ .../scratch}/so_global_optimization.py | 0 52 files changed, 650 insertions(+), 187 deletions(-) create mode 100644 pymoo/algorithms/so_adam.py create mode 100644 pymoo/algorithms/so_gradient_descent.py create mode 100644 pymoo/usage/algorithms/usage_ga_custom.py create mode 100644 pymoo/usage/algorithms/usage_ga_equality.py create mode 100644 pymoo/usage/algorithms/usage_so_adam.py create mode 100644 pymoo/usage/algorithms/usage_so_gradient_descent.py rename pymoo/usage/algorithms/{usage_nelder_mead.py => usage_so_nelder_mead.py} (62%) create mode 100644 tests/scratch/__init__.py create mode 100644 tests/scratch/scratch1.py create mode 100644 tests/scratch/scratch2.py rename {pymoo/algorithms => tests/scratch}/so_global_optimization.py (100%) diff --git a/README.rst b/README.rst index 2484eb521..2ba8026f9 100644 --- a/README.rst +++ b/README.rst @@ -78,7 +78,7 @@ However, for instance executing NSGA2: problem = get_problem("zdt2") - algorithm = NSGA2(pop_size=100, elimate_duplicates=True) + algorithm = NSGA2(pop_size=100, eliminate_duplicates=True) res = minimize(problem, algorithm, diff --git a/benchmark/benchmark_nelder_mead.py b/benchmark/benchmark_nelder_mead.py index 00d002990..e6304caba 100644 --- a/benchmark/benchmark_nelder_mead.py +++ b/benchmark/benchmark_nelder_mead.py @@ -4,10 +4,9 @@ import os import pickle -from pymoo.algorithms.so_genetic_algorithm import ga -from pymoo.algorithms.so_nelder_mead import nelder_mead -from pymoo.factory import get_problem, get_termination -from pymoo.operators.crossover.nelder_mead_crossover import NelderMeadCrossover +from pymoo.algorithms.so_nelder_mead import NelderMead + +from pymoo.factory import get_problem setup = [ "go-amgm", @@ -222,7 +221,7 @@ def add_with_variables(D, problem, n_vars): prefix = "runs" # name of the experiment - name = "nelder-mead-0.3.1" + name = "nelder-mead-0.3.2" # number of runs to execute n_runs = 10 @@ -234,7 +233,7 @@ def add_with_variables(D, problem, n_vars): problem = get_problem(_problem) - method = nelder_mead(n_max_restarts=100) + method = NelderMead(n_max_local_restarts=2) for run in range(1, n_runs + 1): fname = "%s_%s.run" % (_problem, run) diff --git a/benchmark/benchmark_nsga2.py b/benchmark/benchmark_nsga2.py index 3a63a5e92..0c12aab39 100644 --- a/benchmark/benchmark_nsga2.py +++ b/benchmark/benchmark_nsga2.py @@ -4,7 +4,8 @@ import os import pickle -from pymoo.algorithms.nsga2 import nsga2 +from pymoo.algorithms.nsga2 import NSGA2 + from pymoo.factory import get_sampling, get_crossover, get_mutation, get_problem from pymoo.operators.crossover.simulated_binary_crossover import SimulatedBinaryCrossover from pymoo.operators.mutation.polynomial_mutation import PolynomialMutation @@ -90,7 +91,7 @@ prefix = "runs" # name of the experiment - name = "pynsga2-0.3.1" + name = "pynsga2-0.3.2" # number of runs to execute n_runs = 100 @@ -107,7 +108,7 @@ s = setup[_problem] problem = s['problem'] - method = nsga2( + method = NSGA2( pop_size=s['pop_size'], crossover=s['crossover'], mutation=s['mutation'], diff --git a/benchmark/benchmark_nsga3.py b/benchmark/benchmark_nsga3.py index 13a7b1088..6e5d53292 100644 --- a/benchmark/benchmark_nsga3.py +++ b/benchmark/benchmark_nsga3.py @@ -1,7 +1,7 @@ import os import pickle -from pymoo.algorithms.nsga3 import nsga3 +from pymoo.algorithms.nsga3 import NSGA3 from pymoo.factory import get_problem, get_reference_directions from pymoo.operators.crossover.simulated_binary_crossover import SimulatedBinaryCrossover from pymoo.operators.mutation.polynomial_mutation import PolynomialMutation @@ -206,7 +206,7 @@ def get_setup(n_obj): prefix = "runs" # name of the experiment - name = "pynsga3-0.3.1" + name = "pynsga3-0.3.2" # number of runs to execute n_runs = 50 @@ -222,7 +222,7 @@ def get_setup(n_obj): s = setup[_problem] problem = s['problem'] - method = nsga3( + method = NSGA3( s['ref_dirs'], pop_size=s['pop_size'], crossover=s['crossover'], diff --git a/pymoo/algorithms/moead.py b/pymoo/algorithms/moead.py index 81a59a501..7d3770b07 100755 --- a/pymoo/algorithms/moead.py +++ b/pymoo/algorithms/moead.py @@ -130,12 +130,4 @@ def _next(self): pop[N[I]] = off -# ========================================================================================================= -# Interface -# ========================================================================================================= - -def moead(*args, **kwargs): - return MOEAD(*args, **kwargs) - - parse_doc_string(MOEAD.__init__) diff --git a/pymoo/algorithms/nsga2.py b/pymoo/algorithms/nsga2.py index e0fe779f4..ac2f497ce 100644 --- a/pymoo/algorithms/nsga2.py +++ b/pymoo/algorithms/nsga2.py @@ -202,12 +202,4 @@ def calc_crowding_distance(F, filter_out_duplicates=True): return crowding -# ========================================================================================================= -# Interface -# ========================================================================================================= - -def nsga2(*args, **kwargs): - return NSGA2(*args, **kwargs) - - parse_doc_string(NSGA2.__init__) diff --git a/pymoo/algorithms/nsga3.py b/pymoo/algorithms/nsga3.py index 9342593d9..44e0c4890 100644 --- a/pymoo/algorithms/nsga3.py +++ b/pymoo/algorithms/nsga3.py @@ -335,12 +335,4 @@ def calc_niche_count(n_niches, niche_of_individuals): return niche_count -# ========================================================================================================= -# Interface -# ========================================================================================================= - -def nsga3(*args, **kwargs): - return NSGA3(*args, **kwargs) - - parse_doc_string(NSGA3.__init__) diff --git a/pymoo/algorithms/rnsga2.py b/pymoo/algorithms/rnsga2.py index 90c1d91b3..4a1643f0d 100644 --- a/pymoo/algorithms/rnsga2.py +++ b/pymoo/algorithms/rnsga2.py @@ -185,13 +185,4 @@ def calc_norm_pref_distance(A, B, weights, ideal, nadir): return np.reshape(N, (A.shape[0], B.shape[0])) -# ========================================================================================================= -# Interface -# ========================================================================================================= - - -def rnsga2(*args, **kwargs): - return RNSGA2(*args, **kwargs) - - parse_doc_string(RNSGA2.__init__) diff --git a/pymoo/algorithms/rnsga3.py b/pymoo/algorithms/rnsga3.py index a4791d96b..6785134f5 100644 --- a/pymoo/algorithms/rnsga3.py +++ b/pymoo/algorithms/rnsga3.py @@ -244,13 +244,4 @@ def line_plane_intersection(l0, l1, p0, p_no, epsilon=1e-6): return ref_proj -# ========================================================================================================= -# Interface -# ========================================================================================================= - - -def rnsga3(*args, **kwargs): - return RNSGA3(*args, **kwargs) - - parse_doc_string(RNSGA3.__init__) diff --git a/pymoo/algorithms/so_adam.py b/pymoo/algorithms/so_adam.py new file mode 100644 index 000000000..8d3cc61f5 --- /dev/null +++ b/pymoo/algorithms/so_adam.py @@ -0,0 +1,48 @@ +import numpy as np + +from pymoo.algorithms.so_gradient_descent import GradientBasedAlgorithm + + +class Adam(GradientBasedAlgorithm): + + def __init__(self, X, + alpha=0.005, + beta_1=0.9, + beta_2=0.999, + epsilon=1e-8, + **kwargs) -> None: + super().__init__(X, **kwargs) + + self.alpha = alpha + self.beta_1 = beta_1 + self.beta_2 = beta_2 + self.epsilon = epsilon + + self.t = 0 + self.m_t = 0 + self.v_t = 0 + + def apply(self): + X, dX = self.X, self.dX + + self.t += 1 + beta_1, beta_2 = self.beta_1, self.beta_2 + + # update moving average of gradient and squared gradient + self.m_t = beta_1 * self.m_t + (1 - beta_1) * dX + self.v_t = beta_2 * self.v_t + (1 - beta_2) * (dX * dX) + + # calculates the bias-corrected estimates + m_cap = self.m_t / (1 - (beta_1 ** self.t)) + v_cap = self.v_t / (1 - (beta_2 ** self.t)) + + # do the gradient update + self.X = X - (self.alpha * m_cap) / (np.sqrt(v_cap) + self.epsilon) + + def restart(self): + self.t = 0 + self.m_t = 0 + self.v_t = 0 + self.alpha /= 2 + + diff --git a/pymoo/algorithms/so_de.py b/pymoo/algorithms/so_de.py index 05404527d..36e2fb3ad 100755 --- a/pymoo/algorithms/so_de.py +++ b/pymoo/algorithms/so_de.py @@ -2,6 +2,7 @@ from pymoo.algorithms.genetic_algorithm import GeneticAlgorithm from pymoo.docs import parse_doc_string +from pymoo.model.termination import SingleObjectiveToleranceBasedTermination from pymoo.operators.crossover.differental_evolution_crossover import DifferentialEvolutionCrossover from pymoo.operators.crossover.exponential_crossover import ExponentialCrossover from pymoo.operators.crossover.uniform_crossover import UniformCrossover @@ -77,6 +78,7 @@ def __init__(self, survival=None, **kwargs) + self.default_termination = SingleObjectiveToleranceBasedTermination() self.func_display_attrs = disp_single_objective def _next(self): @@ -132,12 +134,4 @@ def _next(self): return pop -# ========================================================================================================= -# Interface -# ========================================================================================================= - -def de(*args, **kwargs): - return DE(*args, **kwargs) - - parse_doc_string(DE.__init__) diff --git a/pymoo/algorithms/so_genetic_algorithm.py b/pymoo/algorithms/so_genetic_algorithm.py index d857f41f6..84d29674d 100644 --- a/pymoo/algorithms/so_genetic_algorithm.py +++ b/pymoo/algorithms/so_genetic_algorithm.py @@ -3,6 +3,7 @@ from pymoo.algorithms.genetic_algorithm import GeneticAlgorithm from pymoo.docs import parse_doc_string from pymoo.model.survival import Survival +from pymoo.model.termination import SingleObjectiveToleranceBasedTermination from pymoo.operators.crossover.simulated_binary_crossover import SimulatedBinaryCrossover from pymoo.operators.mutation.polynomial_mutation import PolynomialMutation from pymoo.operators.sampling.random_sampling import FloatRandomSampling @@ -67,6 +68,7 @@ def __init__(self, **kwargs) self.func_display_attrs = disp_single_objective + self.default_termination = SingleObjectiveToleranceBasedTermination() class FitnessSurvival(Survival): @@ -83,14 +85,4 @@ def _do(self, problem, pop, n_survive, out=None, **kwargs): return pop[np.argsort(F[:, 0])[:n_survive]] -# ========================================================================================================= -# Interface -# ========================================================================================================= - - -def ga(*args, **kwargs): - return GA(*args, **kwargs) - - parse_doc_string(GA.__init__) - diff --git a/pymoo/algorithms/so_gradient_descent.py b/pymoo/algorithms/so_gradient_descent.py new file mode 100644 index 000000000..3fcbf69ff --- /dev/null +++ b/pymoo/algorithms/so_gradient_descent.py @@ -0,0 +1,89 @@ +import numpy as np + +from pymoo.model.algorithm import Algorithm +from pymoo.model.population import Population +from pymoo.model.termination import SingleObjectiveToleranceBasedTermination +from pymoo.operators.repair.out_of_bounds_repair import repair_out_of_bounds +from pymoo.util.display import disp_single_objective + + +class GradientBasedAlgorithm(Algorithm): + + def __init__(self, X, dX=None, objective=0, **kwargs) -> None: + super().__init__(**kwargs) + self.func_display_attrs = disp_single_objective + self.objective = objective + self.n_restarts = 0 + self.default_termination = SingleObjectiveToleranceBasedTermination() + + self.X, self.dX = X, dX + self.F, self.CV = None, None + + if self.X.ndim == 1: + self.X = np.atleast_2d(X) + + def _initialize(self): + self._next() + + def _next(self): + + # create a copy from the current values - if restart is necessary + _X = np.copy(self.X) + + # if the gradient was not provided yet evaluate it + if self.F is None or self.dX is None: + # evaluate the problem and get the information of gradients + F, dX, CV = self.problem.evaluate(self.X, return_values_of=["F", "dF", "CV"]) + + # because we only consider one objective here + F = F[:, [self.objective]] + dX = dX[:, self.objective] + + # increase the evaluation count + self.evaluator.n_eval += len(self.X) + + has_improved = self.F is None or np.any(F < self.F) + is_gradient_valid = np.all(~np.isnan(dX)) + + # if the gradient did lead to an improvement + if has_improved: + + self.F, self.dX, self.CV = F, dX, CV + + # if the gradient is valid and has no nan values + if is_gradient_valid: + + # make the step and check out of bounds for X + self.apply() + self.X = repair_out_of_bounds(self.problem, self.X) + + # set the population object for automatic print + self.pop = Population(len(self.X)).set("X", self.X, "F", self.F, + "CV", self.CV, "feasible", self.CV <= 0) + + # otherwise end the termination form now on + else: + print("WARNING: GRADIENT ERROR", self.dX) + self.termination.force_termination = True + + # otherwise do a restart of the algorithm + else: + self.X = _X + self.restart() + self.n_restarts += 1 + + # set the gradient to none to be ready for the next iteration + self.dX = None + + +class GradientDescent(GradientBasedAlgorithm): + + def __init__(self, X, learning_rate=0.005, **kwargs) -> None: + super().__init__(X, **kwargs) + self.learning_rate = learning_rate + + def restart(self): + self.learning_rate /= 2 + + def apply(self): + self.X = self.X - self.learning_rate * self.dX diff --git a/pymoo/algorithms/so_nelder_mead.py b/pymoo/algorithms/so_nelder_mead.py index 62ddb6d46..f57e259a0 100644 --- a/pymoo/algorithms/so_nelder_mead.py +++ b/pymoo/algorithms/so_nelder_mead.py @@ -1,4 +1,5 @@ import numpy as np +from pymoo.docs import parse_doc_string from pymoo.model.algorithm import Algorithm from pymoo.model.individual import Individual @@ -114,12 +115,43 @@ def max_expansion_factor(point, direction, xl, xu): class NelderMead(Algorithm): def __init__(self, - x0=None, + X=None, func_params=adaptive_params, - termination=NelderAndMeadTermination(xtol=1e-6, ftol=1e-6, n_max_iter=1e6, n_max_evals=1e6), - criterion_local_restart=NelderAndMeadTermination(xtol=1e-2, ftol=1e-2), n_max_local_restarts=0, + criterion_local_restart=NelderAndMeadTermination(xtol=1e-2, ftol=1e-2), **kwargs): + """ + + Parameters + ---------- + X : np.array or Population + The initial point where the search should be based on or the complete initial simplex (number of + dimensions plus 1 points). The population objective can be already evaluated with objective + space values. If a numpy array is provided it is evaluated by the algorithm. + By default it is None which means the search starts at a random point. + + func_params : func + A function that returns the parameters alpha, beta, gamma, delta for the search. By default: + + >>> def adaptive_params(problem): + ... n = problem.n_var + ... + ... alpha = 1 + ... beta = 1 + 2 / n + ... gamma = 0.75 - 1 / (2 * n) + ... delta = 1 - 1 / n + ... return alpha, beta, gamma, delta + + It can be overwritten if necessary. + + n_max_local_restarts : int + This algorithm employs local restarts by starting with a new initial simplex. This can be turned of + by setting it to 0. + + criterion_local_restart : Termination + Provide a termination object which decides whether a local restart should be performed or not. + + """ super().__init__(**kwargs) @@ -132,14 +164,12 @@ def __init__(self, # the scaling used for the initial simplex self.simplex_scaling = None - # the specified termination criterion - self.termination = termination - # the initial point to be used to build the simplex - self.x0 = x0 + self.x0 = X self.opt = None self.func_display_attrs = disp_single_objective + self.default_termination = NelderAndMeadTermination(xtol=1e-6, ftol=1e-6, n_max_iter=1e6, n_max_evals=1e6) # everything needed for local restarts self.n_max_local_restarts = n_max_local_restarts @@ -379,9 +409,4 @@ def initialize_simplex(self, x0): return X -# ========================================================================================================= -# Interface -# ========================================================================================================= - -def nelder_mead(**kwargs): - return NelderMead(**kwargs) +parse_doc_string(NelderMead.__init__) diff --git a/pymoo/algorithms/unsga3.py b/pymoo/algorithms/unsga3.py index ed8f3184b..5b9aee185 100644 --- a/pymoo/algorithms/unsga3.py +++ b/pymoo/algorithms/unsga3.py @@ -47,11 +47,3 @@ def __init__(self, super().__init__(ref_dirs, selection=selection, **kwargs) - -# ========================================================================================================= -# Interface -# ========================================================================================================= - -def unsga3(*args, **kwargs): - return UNSGA3(*args, **kwargs) - diff --git a/pymoo/factory.py b/pymoo/factory.py index d29149da3..791b69e14 100644 --- a/pymoo/factory.py +++ b/pymoo/factory.py @@ -56,26 +56,26 @@ def get_from_list(l, name, args, kwargs): # ========================================================================================================= def get_algorithm_options(): - from pymoo.algorithms.moead import moead - from pymoo.algorithms.nsga2 import nsga2 - from pymoo.algorithms.nsga3 import nsga3 - from pymoo.algorithms.rnsga2 import rnsga2 - from pymoo.algorithms.rnsga3 import rnsga3 - from pymoo.algorithms.so_de import de - from pymoo.algorithms.so_genetic_algorithm import ga - from pymoo.algorithms.unsga3 import unsga3 - from pymoo.algorithms.so_nelder_mead import nelder_mead + from pymoo.algorithms.moead import MOEAD + from pymoo.algorithms.nsga2 import NSGA2 + from pymoo.algorithms.nsga3 import NSGA3 + from pymoo.algorithms.rnsga2 import RNSGA2 + from pymoo.algorithms.rnsga3 import RNSGA3 + from pymoo.algorithms.so_de import DE + from pymoo.algorithms.so_genetic_algorithm import GA + from pymoo.algorithms.unsga3 import UNSGA3 + from pymoo.algorithms.so_nelder_mead import NelderMead ALGORITHMS = [ - ("ga", ga), - ("de", de), - ("nelder-mead", nelder_mead), - ("nsga2", nsga2), - ("rnsga2", rnsga2), - ("nsga3", nsga3), - ("unsga3", unsga3), - ("rnsga3", rnsga3), - ("moead", moead), + ("ga", GA), + ("de", DE), + ("nelder-mead", NelderMead), + ("nsga2", NSGA2), + ("rnsga2", RNSGA2), + ("nsga3", NSGA3), + ("unsga3", UNSGA3), + ("rnsga3", RNSGA3), + ("moead", MOEAD), ] return ALGORITHMS @@ -194,7 +194,7 @@ def get_termination_options(): TERMINATION = [ ("n_eval", MaximumFunctionCallTermination), - ("n_gen", MaximumGenerationTermination), + ("(n_gen|n_iter)", MaximumGenerationTermination), ("igd", IGDTermination), ("x_tol", DesignSpaceToleranceTermination), ("f_tol", ObjectiveSpaceToleranceTermination) diff --git a/pymoo/model/algorithm.py b/pymoo/model/algorithm.py index e20a27cfd..8f692370c 100644 --- a/pymoo/model/algorithm.py +++ b/pymoo/model/algorithm.py @@ -1,4 +1,5 @@ import copy +from abc import abstractmethod import numpy as np @@ -59,6 +60,7 @@ def callback(algorithm): def __init__(self, callback=None, + termination=None, return_least_infeasible=False, **kwargs): @@ -86,7 +88,9 @@ def __init__(self, # the optimization problem as an instance self.problem = None # the termination criterion of the algorithm - self.termination = None + self.termination = termination + # an algorithm can defined the default termination which can be overwritten + self.default_termination = None # the random seed that was used self.seed = None # the pareto-front of the problem - if it exist or passed @@ -130,6 +134,10 @@ def initialize(self, if self.termination is None: self.termination = termination + # if nothing given fall back to default + if self.termination is None: + self.termination = self.default_termination + # set the random seed in the algorithm object self.seed = seed if self.seed is None: @@ -254,6 +262,14 @@ def _display(self, disp, header=False): def _finalize(self): pass + @abstractmethod + def _initialize(self): + pass + + @abstractmethod + def _next(self): + pass + def filter_optimum(pop, least_infeasible=False): diff --git a/pymoo/model/plot.py b/pymoo/model/plot.py index f7a3ad38b..81cc0059c 100644 --- a/pymoo/model/plot.py +++ b/pymoo/model/plot.py @@ -158,7 +158,8 @@ def show(self, **kwargs): # in a notebook the plot method need not to be called explicitly if not in_notebook() and matplotlib.get_backend() != "agg": - self.fig.show(**kwargs) + plt.show(**kwargs) + plt.close() def save(self, fname, **kwargs): self.plot_if_not_done_yet() diff --git a/pymoo/model/population.py b/pymoo/model/population.py index 670fde736..e77bb7600 100644 --- a/pymoo/model/population.py +++ b/pymoo/model/population.py @@ -106,7 +106,7 @@ def pop_from_array_or_individual(array, pop=None): if isinstance(array, Population): pop = array elif isinstance(array, np.ndarray): - pop = pop.new("X", [array]) + pop = pop.new("X", np.atleast_2d(array)) elif isinstance(array, Individual): pop = Population(1) pop[0] = array diff --git a/pymoo/model/problem.py b/pymoo/model/problem.py index 08f79745a..507b22df1 100644 --- a/pymoo/model/problem.py +++ b/pymoo/model/problem.py @@ -1,6 +1,7 @@ import multiprocessing import warnings from abc import abstractmethod +from multiprocessing.pool import ThreadPool import autograd import autograd.numpy as anp @@ -80,9 +81,6 @@ def __init__(self, self._pareto_set = None self._ideal_point, self._nadir_point = None, None - # calculate the boundary points - self.pareto_front(exception_if_failing=False) - # actually defines what _evaluate is setting during the evaluation if evaluation_of == "auto": # by default F is set, and G if the problem does have constraints @@ -101,7 +99,6 @@ def __init__(self, # store the callback if defined self.callback = callback - # return the maximum objective values of the pareto front def nadir_point(self): """ Returns @@ -111,9 +108,19 @@ def nadir_point(self): If single-objective, it returns the best possible solution which is equal to the ideal point. """ + # if the ideal point has not been calculated yet + if self._nadir_point is None: + + # calculate the pareto front if not happened yet + if self._pareto_front is None: + self.pareto_front() + + # if already done or it was successful - calculate the ideal point + if self._pareto_front is not None: + self._ideal_point = np.max(self._pareto_front, axis=0) + return self._nadir_point - # return the minimum values of the pareto front def ideal_point(self): """ Returns @@ -122,6 +129,18 @@ def ideal_point(self): The ideal point for a multi-objective problem. If single-objective it returns the best possible solution. """ + + # if the ideal point has not been calculated yet + if self._ideal_point is None: + + # calculate the pareto front if not happened yet + if self._pareto_front is None: + self.pareto_front() + + # if already done or it was successful - calculate the ideal point + if self._pareto_front is not None: + self._ideal_point = np.min(self._pareto_front, axis=0) + return self._ideal_point def pareto_front(self, *args, use_cache=True, exception_if_failing=True, **kwargs): @@ -149,10 +168,6 @@ def pareto_front(self, *args, use_cache=True, exception_if_failing=True, **kwarg pf = self._calc_pareto_front(*args, **kwargs) if pf is not None: self._pareto_front = at_least_2d_array(pf) - self._ideal_point = np.min(self._pareto_front, axis=0) - self._nadir_point = np.max(self._pareto_front, axis=0) - else: - self._pareto_front, self._ideal_point, self._nadir_point = None, None, None except Exception as e: if exception_if_failing: @@ -352,7 +367,7 @@ def func(_x): else: n_threads = _params[0] - with multiprocessing.Pool(n_threads) as pool: + with ThreadPool(n_threads) as pool: params = [] for k in range(len(X)): params.append([X[k], calc_gradient, self._evaluate, args, kwargs]) diff --git a/pymoo/model/termination.py b/pymoo/model/termination.py index e6b0ca60f..a919d8f0e 100644 --- a/pymoo/model/termination.py +++ b/pymoo/model/termination.py @@ -1,4 +1,5 @@ import numpy as np +from pymoo.util.misc import vectorized_cdist from pymoo.performance_indicator.igd import IGD from pymoo.performance_indicator.igd_plus import IGDPlus @@ -42,6 +43,10 @@ class ToleranceBasedTermination(Termination): def __init__(self, tol=0.001, n_last=20, n_max_gen=1000, nth_gen=5) -> None: super().__init__() + + if n_last < 2: + raise Exception("At last the last 2 elements need to be considered!") + self.tol = tol self.nth_gen = nth_gen self.n_last = n_last @@ -162,3 +167,60 @@ def __init__(self, min_igd, pf) -> None: def _do_continue(self, algorithm): F = algorithm.pop.get("F") return self.obj.calc(F) > self.igd + + +class SingleObjectiveToleranceBasedTermination(DesignSpaceToleranceTermination): + + def __init__(self, + x_tol=1e-6, + f_tol=1e-6, + f_tol_abs=1e-8, + n_last=20, + n_max_gen=1000, + nth_gen=5, + **kwargs) -> None: + super().__init__(n_last=n_last, + n_max_gen=n_max_gen, + nth_gen=nth_gen, + **kwargs) + self.x_tol = x_tol + self.f_tol = f_tol + self.f_tol_abs = f_tol_abs + + self.n_restarts = None + self.F_min = np.inf + + def _store(self, algorithm): + super()._store(algorithm) + if "n_restarts" in algorithm.__dict__: + self.n_restarts = algorithm.n_restarts + + # store the current minimum + F = algorithm.pop.get("F") + self.F_min = min(self.F_min, F.min()) + + return algorithm.pop.get("X"), F + + def _decide(self): + # get the beginning and the end of the window + current = normalize(self.history[0][0], x_min=self.xl, x_max=self.xu) + last = normalize(self.history[-1][0], x_min=self.xl, x_max=self.xu) + + # now analyze the change in X space always from the closest two solutions + I = vectorized_cdist(current, last).argmin(axis=1) + avg_dist = np.sqrt((current - last[I]) ** 2).mean() + + # whether the change was less than x space tolerance + x_tol = avg_dist < self.x_tol + + # now check the F space + current = self.history[0][1].min() + last = self.history[-1][1].min() + + # the absolute difference of current to last f + f_tol_abs = last - current < self.f_tol_abs + + # now the relative tolerance which is usually more important + f_tol = last / self.F_min - current / self.F_min < self.f_tol + + return not (x_tol or f_tol_abs or f_tol) diff --git a/pymoo/operators/crossover/simulated_binary_crossover.py b/pymoo/operators/crossover/simulated_binary_crossover.py index 925533202..6bbefcd63 100644 --- a/pymoo/operators/crossover/simulated_binary_crossover.py +++ b/pymoo/operators/crossover/simulated_binary_crossover.py @@ -18,6 +18,9 @@ def _do(self, problem, X, **kwargs): # boundaries of the problem xl, xu = problem.xl, problem.xu + #if np.any(X < xl) or np.any(X > xu): + # raise Exception("Simulated binary crossover requires all variables to be in bounds!") + # crossover mask that will be used in the end do_crossover = np.full(X[0].shape, True) diff --git a/pymoo/operators/selection/tournament_selection.py b/pymoo/operators/selection/tournament_selection.py index 10ae2022d..e781d35c5 100644 --- a/pymoo/operators/selection/tournament_selection.py +++ b/pymoo/operators/selection/tournament_selection.py @@ -71,4 +71,4 @@ def compare(a, a_val, b, b_val, method, return_random_if_equal=False): else: return None else: - raise Exception("Unknown method.") \ No newline at end of file + raise Exception("Unknown method.") diff --git a/pymoo/performance_indicator/kktpm.py b/pymoo/performance_indicator/kktpm.py index 0db62e530..a290f849f 100644 --- a/pymoo/performance_indicator/kktpm.py +++ b/pymoo/performance_indicator/kktpm.py @@ -140,7 +140,7 @@ def calc(self, X, problem, ideal_point=None, utopian_epsilon=1e-4, rho=1e-3): kktpm[i] = _kktpm fval[i] = _fval - return kktpm, fval + return kktpm def solve(A, b, method="elim"): diff --git a/pymoo/problems/single/knapsack.py b/pymoo/problems/single/knapsack.py index 3090a586f..b93ff5840 100644 --- a/pymoo/problems/single/knapsack.py +++ b/pymoo/problems/single/knapsack.py @@ -10,12 +10,7 @@ def __init__(self, P, # profit of each item C, # maximum capacity ): - super().__init__(n_var=n_items, n_obj=1, n_constr=0, xl=0, xu=1, type_var=anp.bool) - - self.n_var = n_items - self.n_constr = 1 - self.n_obj = 1 - self.func = self._evaluate + super().__init__(n_var=n_items, n_obj=1, n_constr=1, xl=0, xu=1, type_var=anp.bool) self.W = W self.P = P diff --git a/pymoo/usage/algorithms/usage_custom_output.py b/pymoo/usage/algorithms/usage_custom_output.py index 9c37e2308..762e99b3d 100644 --- a/pymoo/usage/algorithms/usage_custom_output.py +++ b/pymoo/usage/algorithms/usage_custom_output.py @@ -1,7 +1,6 @@ from pymoo.algorithms.nsga2 import NSGA2 from pymoo.factory import get_problem from pymoo.optimize import minimize -from pymoo.visualization.scatter import Scatter def my_callback(algorithm): @@ -16,11 +15,7 @@ def my_callback(algorithm): res = minimize(problem, algorithm, - ('n_gen', 200), + ('n_gen', 10), seed=1, verbose=False) -plot = Scatter() -plot.add(problem.pareto_front(), plot_type="line", color="black", alpha=0.7) -plot.add(res.F, color="red") -plot.show() diff --git a/pymoo/usage/algorithms/usage_de.py b/pymoo/usage/algorithms/usage_de.py index b4c430c2f..31900a436 100644 --- a/pymoo/usage/algorithms/usage_de.py +++ b/pymoo/usage/algorithms/usage_de.py @@ -18,7 +18,7 @@ res = minimize(problem, algorithm, - termination=('n_gen', 250), - seed=1) + seed=1, + verbose=False) print("Best solution found: \nX = %s\nF = %s" % (res.X, res.F)) \ No newline at end of file diff --git a/pymoo/usage/algorithms/usage_ga.py b/pymoo/usage/algorithms/usage_ga.py index 6413fa11b..ac52e1066 100644 --- a/pymoo/usage/algorithms/usage_ga.py +++ b/pymoo/usage/algorithms/usage_ga.py @@ -11,6 +11,7 @@ res = minimize(problem, algorithm, termination=('n_gen', 50), + seed=1, verbose=False) print("Best solution found: \nX = %s\nF = %s" % (res.X, res.F)) diff --git a/pymoo/usage/algorithms/usage_ga_custom.py b/pymoo/usage/algorithms/usage_ga_custom.py new file mode 100644 index 000000000..981631a02 --- /dev/null +++ b/pymoo/usage/algorithms/usage_ga_custom.py @@ -0,0 +1,100 @@ +import numpy as np +from pymoo.model.problem import Problem + + +class SubsetProblem(Problem): + def __init__(self, + L, + n_max + ): + super().__init__(n_var=len(L), n_obj=1, n_constr=1, elementwise_evaluation=True) + self.L = L + self.n_max = n_max + + def _evaluate(self, x, out, *args, **kwargs): + out["F"] = np.sum(self.L[x]) + out["G"] = (self.n_max - np.sum(x)) ** 2 + + +# create the actual problem to be solved +np.random.seed(1) +L = np.array([np.random.randint(100) for _ in range(100)]) +n_max = 10 +problem = SubsetProblem(L, n_max) + +from pymoo.model.crossover import Crossover +from pymoo.model.mutation import Mutation +from pymoo.model.sampling import Sampling + + +class MySampling(Sampling): + + def _do(self, problem, n_samples, **kwargs): + X = np.full((n_samples, problem.n_var), False, dtype=np.bool) + + for k in range(n_samples): + I = np.random.permutation(problem.n_var)[:problem.n_max] + X[k, I] = True + + return X + + +class BinaryCrossover(Crossover): + def __init__(self): + super().__init__(2, 1) + + def _do(self, problem, X, **kwargs): + n_parents, n_matings, n_var = X.shape + + _X = np.full((self.n_offsprings, n_matings, problem.n_var), False) + + for k in range(n_matings): + p1, p2 = X[0, k], X[1, k] + + both_are_true = np.logical_and(p1, p2) + _X[0, k, both_are_true] = True + + n_remaining = problem.n_max - np.sum(both_are_true) + + I = np.where(np.logical_xor(p1, p2))[0] + + S = I[np.random.permutation(len(I))][:n_remaining] + _X[0, k, S] = True + + return _X + + +class MyMutation(Mutation): + def _do(self, problem, X, **kwargs): + for i in range(X.shape[0]): + X[i, :] = X[i, :] + is_false = np.where(np.logical_not(X[i, :]))[0] + is_true = np.where(X[i, :])[0] + X[i, np.random.choice(is_false)] = True + X[i, np.random.choice(is_true)] = False + + return X + + +from pymoo.algorithms.so_genetic_algorithm import GA +from pymoo.optimize import minimize + +algorithm = GA( + pop_size=100, + sampling=MySampling(), + crossover=BinaryCrossover(), + mutation=MyMutation(), + eliminate_duplicates=True) + +res = minimize(problem, + algorithm, + ('n_gen', 60), + seed=1, + verbose=True) + +print("Function value: %s" % res.F[0]) +print("Subset:", np.where(res.X)[0]) + +opt = np.sort(np.argsort(L)[:n_max]) +print("Optimal Subset:", opt) +print("Optimal Function Value: %s" % L[opt].sum()) diff --git a/pymoo/usage/algorithms/usage_ga_equality.py b/pymoo/usage/algorithms/usage_ga_equality.py new file mode 100644 index 000000000..96a88e474 --- /dev/null +++ b/pymoo/usage/algorithms/usage_ga_equality.py @@ -0,0 +1,54 @@ +import numpy as np + +from pymoo.model.problem import Problem + + +class MyProblem(Problem): + + def __init__(self): + super().__init__(n_var=3, + n_obj=2, + n_constr=1, + xl=np.array([-2, -2, -2]), + xu=np.array([2, 2, 2])) + + def _evaluate(self, x, out, *args, **kwargs): + f1 = x[:, 0] ** 2 + x[:, 1] ** 2 + f2 = (x[:, 0] - 1) ** 2 + x[:, 1] ** 2 + g1 = (x[:, 0] + x[:, 2] - 2) ** 2 - 1e-5 + + out["F"] = np.column_stack([f1, f2]) + out["G"] = g1 + + +from pymoo.model.repair import Repair + + +class MyRepair(Repair): + + def _do(self, problem, pop, **kwargs): + for k in range(len(pop)): + x = pop[k].X + if np.random.random() < 0.5: + x[2] = 2 - x[0] + else: + x[0] = 2 - x[2] + return pop + + +from pymoo.algorithms.nsga2 import NSGA2 + +algorithm = NSGA2(pop_size=100, repair=MyRepair(), eliminate_duplicates=True) + +from pymoo.optimize import minimize +from pymoo.visualization.scatter import Scatter + +res = minimize(MyProblem(), + algorithm, + ('n_gen', 20), + seed=1, + verbose=True) + +plot = Scatter() +plot.add(res.F, color="red") +plot.show() diff --git a/pymoo/usage/algorithms/usage_moead.py b/pymoo/usage/algorithms/usage_moead.py index 665e48bf7..2f107bc0f 100644 --- a/pymoo/usage/algorithms/usage_moead.py +++ b/pymoo/usage/algorithms/usage_moead.py @@ -8,7 +8,8 @@ get_reference_directions("das-dennis", 3, n_partitions=12), n_neighbors=15, decomposition="pbi", - prob_neighbor_mating=0.7 + prob_neighbor_mating=0.7, + seed=1 ) res = minimize(problem, algorithm, termination=('n_gen', 200)) diff --git a/pymoo/usage/algorithms/usage_nsga2.py b/pymoo/usage/algorithms/usage_nsga2.py index 60976e756..95ed1f453 100644 --- a/pymoo/usage/algorithms/usage_nsga2.py +++ b/pymoo/usage/algorithms/usage_nsga2.py @@ -5,7 +5,7 @@ problem = get_problem("zdt2") -algorithm = NSGA2(pop_size=100, elimate_duplicates=True) +algorithm = NSGA2(pop_size=100, eliminate_duplicates=True) res = minimize(problem, algorithm, diff --git a/pymoo/usage/algorithms/usage_nsga2_binary.py b/pymoo/usage/algorithms/usage_nsga2_binary.py index 9c4bbf4c0..620a3379e 100644 --- a/pymoo/usage/algorithms/usage_nsga2_binary.py +++ b/pymoo/usage/algorithms/usage_nsga2_binary.py @@ -9,7 +9,7 @@ sampling=get_sampling("bin_random"), crossover=get_crossover("bin_two_point"), mutation=get_mutation("bin_bitflip"), - elimate_duplicates=True) + eliminate_duplicates=True) res = minimize(problem, algorithm, diff --git a/pymoo/usage/algorithms/usage_nsga2_custom.py b/pymoo/usage/algorithms/usage_nsga2_custom.py index 585a8ba40..1269ad9eb 100644 --- a/pymoo/usage/algorithms/usage_nsga2_custom.py +++ b/pymoo/usage/algorithms/usage_nsga2_custom.py @@ -41,24 +41,38 @@ def _do(self, problem, n_samples, **kwargs): class MyCrossover(Crossover): def __init__(self): + + # define the crossover: number of parents and number of offsprings super().__init__(2, 2) def _do(self, problem, X, **kwargs): + + # The input of has the following shape (n_parents, n_matings, n_var) _, n_matings, n_var = X.shape + + # The output owith the shape (n_offsprings, n_matings, n_var) + # Because there the number of parents and offsprings are equal it keeps the shape of X Y = np.full_like(X, None, dtype=np.object) + # for each mating provided for k in range(n_matings): - a, b = X[0, k], X[1, k] - off_a, off_b = np.full(problem.n_characters, "_"), np.full(problem.n_characters, "_") - rand = np.random.random(problem.n_characters) + # get the first and the second parent + a, b = X[0, k, 0], X[1, k, 0] - off_a[rand < 0.5] = a - off_a[rand >= 0.5] = b + # prepare the offsprings + off_a = ["_"] * problem.n_characters + off_b = ["_"] * problem.n_characters - off_b[rand < 0.5] = b - off_b[rand >= 0.5] = a + for i in range(problem.n_characters): + if np.random.random() < 0.5: + off_a[i] = a[i] + off_b[i] = b[i] + else: + off_a[i] = b[i] + off_b[i] = a[i] + # join the character list and set the output Y[0, k, 0], Y[1, k, 0] = "".join(off_a), "".join(off_b) return Y @@ -71,9 +85,7 @@ def __init__(self): def _do(self, problem, X, **kwargs): for i in range(len(X)): if np.random.random() < 0.5: - mut = [c if np.random.random() > 1 / problem.n_characters else np.random.choice(problem.ALPHABET) for c - in X[i, 0]] - X[i, 0] = "".join(mut) + X[i, 0] = "".join(np.array([e for e in X[i, 0]])[np.random.permutation(problem.n_characters)]) return X @@ -90,7 +102,7 @@ def func_is_duplicate(pop, *other, epsilon=1e-20, **kwargs): for val in e: H.add(val.X[0]) - for i, (val, ) in enumerate(pop.get("X")): + for i, (val,) in enumerate(pop.get("X")): if val in H: is_duplicate[i] = True H.add(val) diff --git a/pymoo/usage/algorithms/usage_rnsga3.py b/pymoo/usage/algorithms/usage_rnsga3.py index b823e21a4..45959d14a 100644 --- a/pymoo/usage/algorithms/usage_rnsga3.py +++ b/pymoo/usage/algorithms/usage_rnsga3.py @@ -4,7 +4,6 @@ from pymoo.algorithms.rnsga3 import RNSGA3 from pymoo.factory import get_problem from pymoo.optimize import minimize -from pymoo.util.reference_direction import UniformReferenceDirectionFactory from pymoo.visualization.scatter import Scatter problem = get_problem("zdt1") @@ -23,6 +22,7 @@ algorithm=algorithm, termination=('n_gen', 300), pf=pf, + seed=1, verbose=False) reference_directions = res.algorithm.survival.ref_dirs @@ -37,6 +37,7 @@ # END rnsga3 # START rnsga3_3d +from pymoo.util.reference_direction import UniformReferenceDirectionFactory # Get problem problem = get_problem("dtlz4", n_var=12, n_obj=3) @@ -56,6 +57,7 @@ algorithm, termination=('n_gen', 300), pf=pf, + seed=1, verbose=False) diff --git a/pymoo/usage/algorithms/usage_so_adam.py b/pymoo/usage/algorithms/usage_so_adam.py new file mode 100644 index 000000000..06e7d9b5e --- /dev/null +++ b/pymoo/usage/algorithms/usage_so_adam.py @@ -0,0 +1,20 @@ +import numpy as np + +from pymoo.algorithms.so_adam import Adam +from pymoo.factory import get_problem +from pymoo.optimize import minimize + +problem = get_problem("rosenbrock") + +X = np.random.random((1, problem.n_var)) + +algorithm = Adam(X) + +res = minimize(problem, + algorithm, + ("n_evals", 1000), + seed=2, + verbose=True) + +print(res.X) +print(res.F) diff --git a/pymoo/usage/algorithms/usage_so_gradient_descent.py b/pymoo/usage/algorithms/usage_so_gradient_descent.py new file mode 100644 index 000000000..182ecb878 --- /dev/null +++ b/pymoo/usage/algorithms/usage_so_gradient_descent.py @@ -0,0 +1,19 @@ +import numpy as np + +from pymoo.algorithms.so_gradient_descent import GradientDescent +from pymoo.factory import get_problem +from pymoo.optimize import minimize + +problem = get_problem("sphere") + +X = np.random.random(problem.n_var) + +algorithm = GradientDescent(X) + +res = minimize(problem, + algorithm, + seed=1, + verbose=True) + +print(res.X) +print(res.F) diff --git a/pymoo/usage/algorithms/usage_nelder_mead.py b/pymoo/usage/algorithms/usage_so_nelder_mead.py similarity index 62% rename from pymoo/usage/algorithms/usage_nelder_mead.py rename to pymoo/usage/algorithms/usage_so_nelder_mead.py index 732fe7e6e..f4ac7e874 100644 --- a/pymoo/usage/algorithms/usage_nelder_mead.py +++ b/pymoo/usage/algorithms/usage_so_nelder_mead.py @@ -1,10 +1,10 @@ from pymoo.algorithms.so_nelder_mead import NelderMead -from pymoo.factory import get_problem, get_algorithm +from pymoo.factory import get_problem from pymoo.optimize import minimize -problem = get_problem("go-xinsheyang04") +problem = get_problem("sphere") -algorithm = NelderMead(n_max_restarts=100) +algorithm = NelderMead(n_max_restarts=10) res = minimize(problem, algorithm, diff --git a/pymoo/usage/usage_repair.py b/pymoo/usage/usage_repair.py index 976c328b6..c8ba458d7 100644 --- a/pymoo/usage/usage_repair.py +++ b/pymoo/usage/usage_repair.py @@ -49,7 +49,7 @@ def _do(self, problem, pop, **kwargs): crossover=get_crossover("bin_hux"), mutation=get_mutation("bin_bitflip"), repair=ConsiderMaximumWeightRepair(), - elimate_duplicates=True) + eliminate_duplicates=True) res = minimize(problem, algorithm, diff --git a/pymoo/util/display.py b/pymoo/util/display.py index 25b96266e..2bf9194e8 100644 --- a/pymoo/util/display.py +++ b/pymoo/util/display.py @@ -74,9 +74,10 @@ def disp_multi_objective(problem, evaluator, algorithm, pf=None): if problem.n_obj == 2: attrs.append(('hv', format_float(Hypervolume(pf=pf).calc(F[feasible])), width)) else: - attrs.append(('igd', "-", width)) - attrs.append(('gd', "-", width)) - if problem.n_obj == 2: - attrs.append(('hv', "-", width)) + if pf is not None: + attrs.append(('igd', "-", width)) + attrs.append(('gd', "-", width)) + if problem.n_obj == 2: + attrs.append(('hv', "-", width)) return attrs diff --git a/pymoo/util/reference_direction.py b/pymoo/util/reference_direction.py index 17f392f3e..3e680a253 100644 --- a/pymoo/util/reference_direction.py +++ b/pymoo/util/reference_direction.py @@ -13,17 +13,19 @@ class ReferenceDirectionFactory: - def __init__(self, n_dim, scaling=None, lexsort=True) -> None: + def __init__(self, n_dim, scaling=None, lexsort=True, verbose=False, seed=None, **kwargs) -> None: super().__init__() self.n_dim = n_dim self.scaling = scaling self.lexsort = lexsort + self.verbose = verbose + self.seed = seed def do(self, seed=None): # set the random seed if it is provided - if seed is not None: - np.random.seed(seed) + if self.seed is not None: + np.random.seed(self.seed) if self.n_dim == 1: return np.array([[1.0]]) diff --git a/pymoo/vendor/global_opt.py b/pymoo/vendor/global_opt.py index 2679bea02..29378359e 100644 --- a/pymoo/vendor/global_opt.py +++ b/pymoo/vendor/global_opt.py @@ -32,7 +32,6 @@ def _evaluate(self, x, out, *args, **kwargs): F[np.isnan(F)] = np.inf out["F"] = F - def success(self, x, **kwargs): return self.object.success(x, **kwargs) @@ -244,5 +243,5 @@ def get_global_optimization_problem_options(): if isinstance(clazz, type) and issubclass(clazz, Benchmark): name = clazz.__name__ print(f"\"go-{name.lower()}\",") - #print(f"(\"go-{name.lower()}\", GlobalOptimizationProblem, {{\"clazz\":{name}}}),") + # print(f"(\"go-{name.lower()}\", GlobalOptimizationProblem, {{\"clazz\":{name}}}),") print("done") diff --git a/pymoo/vendor/go_benchmark_functions/go_funcs_G.py b/pymoo/vendor/go_benchmark_functions/go_funcs_G.py index 8b61a0a9a..c2136087e 100755 --- a/pymoo/vendor/go_benchmark_functions/go_funcs_G.py +++ b/pymoo/vendor/go_benchmark_functions/go_funcs_G.py @@ -211,6 +211,9 @@ def __init__(self, dimensions=3): self._bounds = list(zip([0.0] * self.N, [50.0] * self.N)) + # we have added this line because otherwise you might divide by zero + self._bounds[0] = (1e-120, 50.0) + self.global_optimum = [[50.0, 25.0, 1.5]] self.fglob = 0.0 diff --git a/pymoo/vendor/go_benchmark_functions/go_funcs_L.py b/pymoo/vendor/go_benchmark_functions/go_funcs_L.py index aec5b80e5..f8fac3801 100755 --- a/pymoo/vendor/go_benchmark_functions/go_funcs_L.py +++ b/pymoo/vendor/go_benchmark_functions/go_funcs_L.py @@ -121,7 +121,7 @@ def __init__(self, dimensions=6): self.global_optimum = [[]] - self.mminima = [-1.0, -3.0, -6.0, -9.103852, -12.712062, + self.minima = [-1.0, -3.0, -6.0, -9.103852, -12.712062, -16.505384, -19.821489, -24.113360, -28.422532, -32.765970, -37.967600, -44.326801, -47.845157, -52.322627, -56.815742, -61.317995, -66.530949, diff --git a/pymoo/version.py b/pymoo/version.py index e4a0f8070..f9aa3e110 100644 --- a/pymoo/version.py +++ b/pymoo/version.py @@ -1 +1 @@ -__version__ = "0.3.2.dev" +__version__ = "0.3.2" diff --git a/tests/algorithms/test_algorithms.py b/tests/algorithms/test_algorithms.py index 17301746f..8651c26ad 100644 --- a/tests/algorithms/test_algorithms.py +++ b/tests/algorithms/test_algorithms.py @@ -3,20 +3,32 @@ import numpy as np from pymoo.algorithms.nsga2 import NSGA2 -from pymoo.algorithms.nsga2 import nsga2 from pymoo.factory import get_problem, Problem, ZDT -from pymoo.factory import get_sampling, get_crossover, get_mutation -from pymoo.factory import get_termination from pymoo.optimize import minimize +class MyThreadedProblem(Problem): + + def __init__(self): + super().__init__(n_var=2, + n_obj=1, + n_constr=0, + elementwise_evaluation=True, + parallelization=("threads", 4), + xl=np.array([0, 0]), + xu=np.array([100, 100])) + + def _evaluate(self, x, out, *args, **kwargs): + out["F"] = x[0] + x[1] + class AlgorithmTest(unittest.TestCase): def test_same_seed_same_result(self): problem = get_problem("zdt3") - algorithm = nsga2(pop_size=100, elimate_duplicates=True) + algorithm = NSGA2(pop_size=100, eliminate_duplicates=True) res1 = minimize(problem, algorithm, ('n_gen', 20), seed=1) + np.random.seed(200) res2 = minimize(problem, algorithm, ('n_gen', 20), seed=1) self.assertEqual(res1.X.shape, res2.X.shape) @@ -30,7 +42,7 @@ def _evaluate(self, x, out, *args, **kwargs): f2 = g * (1 - np.power((f1 / g), 0.5)) out["F"] = np.column_stack([f1, f2]) - algorithm = nsga2(pop_size=100, elimate_duplicates=True) + algorithm = NSGA2(pop_size=100, eliminate_duplicates=True) minimize(ZDT1NoPF(), algorithm, ('n_gen', 20), seed=1, verbose=True) def test_no_feasible_solution_found(self): @@ -67,5 +79,13 @@ def _evaluate(self, x, out, *args, **kwargs): self.assertEqual(res.CV, 1) + def test_thread_pool(self): + minimize(MyThreadedProblem(), + NSGA2(), + ("n_gen", 10), + seed=1, + save_history=False) + + if __name__ == '__main__': unittest.main() diff --git a/tests/algorithms/test_nelder_mead.py b/tests/algorithms/test_nelder_mead.py index ad22c4803..f0522f657 100644 --- a/tests/algorithms/test_nelder_mead.py +++ b/tests/algorithms/test_nelder_mead.py @@ -3,7 +3,7 @@ import numpy as np from scipy.optimize import minimize as scipy_minimize -from pymoo.algorithms.so_nelder_mead import nelder_mead +from pymoo.algorithms.so_nelder_mead import NelderMead from pymoo.factory import get_problem, get_termination from pymoo.optimize import minimize @@ -14,22 +14,22 @@ def test_no_bounds(self): problem = get_problem("rastrigin") problem.xl = None problem.xu = None - method = nelder_mead(x0=np.array([1, 1]), max_restarts=0) + method = NelderMead(X=np.array([1, 1]), max_restarts=0) minimize(problem, method, verbose=False) def test_with_bounds_no_restart(self): problem = get_problem("rastrigin") - method = nelder_mead(x0=np.array([1, 1]), max_restarts=0) + method = NelderMead(X=np.array([1, 1]), max_restarts=0) minimize(problem, method, verbose=False) def test_with_bounds_no_initial_point(self): problem = get_problem("rastrigin") - method = nelder_mead(max_restarts=0) + method = NelderMead(max_restarts=0) minimize(problem, method, verbose=False) def test_with_bounds_with_restart(self): problem = get_problem("rastrigin") - method = nelder_mead(x0=np.array([1, 1]), max_restarts=2) + method = NelderMead(X=np.array([1, 1]), max_restarts=2) minimize(problem, method, verbose=False) def test_against_scipy(self): @@ -56,7 +56,7 @@ def callback(x): hist.append(x) problem.callback = callback - minimize(problem, nelder_mead(x0=x0, max_restarts=0, termination=get_termination("n_eval", len(hist_scipy)))) + minimize(problem, NelderMead(X=x0, max_restarts=0, termination=get_termination("n_eval", len(hist_scipy)))) hist = np.row_stack(hist)[:len(hist_scipy)] self.assertTrue(np.all(hist - hist_scipy < 1e-7)) diff --git a/tests/operators/test_crossover.py b/tests/operators/test_crossover.py index 1a76820db..48f196243 100644 --- a/tests/operators/test_crossover.py +++ b/tests/operators/test_crossover.py @@ -1,7 +1,7 @@ import unittest -from pymoo.algorithms.nsga2 import nsga2 -from pymoo.algorithms.so_genetic_algorithm import ga +from pymoo.algorithms.nsga2 import NSGA2 +from pymoo.algorithms.so_genetic_algorithm import GA from pymoo.factory import get_crossover, get_problem from pymoo.optimize import minimize @@ -12,12 +12,12 @@ def test_crossover(self): for crossover in ['real_de', 'real_sbx', 'real_exp']: print(crossover) - method = ga(pop_size=20, crossover=get_crossover(crossover, prob=0.95)) + method = GA(pop_size=20, crossover=get_crossover(crossover, prob=0.95)) minimize(get_problem("sphere"), method, ("n_gen", 20)) for crossover in ['bin_ux', 'bin_hux', 'bin_one_point', 'bin_two_point']: print(crossover) - method = nsga2(pop_size=20, crossover=get_crossover(crossover, prob=0.95)) + method = NSGA2(pop_size=20, crossover=get_crossover(crossover, prob=0.95)) minimize(get_problem("zdt5"), method, ("n_gen", 20)) diff --git a/tests/scratch/__init__.py b/tests/scratch/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/scratch/scratch1.py b/tests/scratch/scratch1.py new file mode 100644 index 000000000..043b8db72 --- /dev/null +++ b/tests/scratch/scratch1.py @@ -0,0 +1,27 @@ +from pymoo.algorithms.nsga2 import NSGA2 +from pymoo.factory import get_problem +from pymoo.optimize import minimize +import numpy as np + +problem = get_problem("zdt3", n_var=5) + +np.random.seed(10) +print(np.random.random()) + + +def my_callback(method): + X = np.loadtxt("gen_%s.csv" % method.n_gen) + _X = method.pop.get("X") + if np.any(np.abs(X - _X) > 1e-8): + print("test") + + +algorithm = NSGA2(pop_size=100, + callback=my_callback, + eliminate_duplicates=False) + +res = minimize(problem, + algorithm, + ('n_gen', 20), + seed=1, + verbose=True) diff --git a/tests/scratch/scratch2.py b/tests/scratch/scratch2.py new file mode 100644 index 000000000..5aaa40791 --- /dev/null +++ b/tests/scratch/scratch2.py @@ -0,0 +1,17 @@ +from pymoo.factory import get_problem + +from pymoo.vendor.global_opt import get_global_optimization_problem_options + +problems = get_global_optimization_problem_options() + +for e in problems: + name = e[2]["clazz"].__name__ + string = e[0] + + p = get_problem(e[0]) + + n_constr = p.n_constr + if n_constr == 0: + n_constr = "" + + print('|%s|%s|%s|"%s"|' % (name, p.n_var, n_constr, string)) diff --git a/pymoo/algorithms/so_global_optimization.py b/tests/scratch/so_global_optimization.py similarity index 100% rename from pymoo/algorithms/so_global_optimization.py rename to tests/scratch/so_global_optimization.py