pymoo 0.6.1.5.dev0__cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pymoo might be problematic. Click here for more details.
- pymoo/__init__.py +3 -0
- pymoo/algorithms/__init__.py +0 -0
- pymoo/algorithms/base/__init__.py +0 -0
- pymoo/algorithms/base/bracket.py +38 -0
- pymoo/algorithms/base/genetic.py +109 -0
- pymoo/algorithms/base/line.py +62 -0
- pymoo/algorithms/base/local.py +39 -0
- pymoo/algorithms/base/meta.py +79 -0
- pymoo/algorithms/hyperparameters.py +89 -0
- pymoo/algorithms/moo/__init__.py +0 -0
- pymoo/algorithms/moo/age.py +310 -0
- pymoo/algorithms/moo/age2.py +194 -0
- pymoo/algorithms/moo/ctaea.py +298 -0
- pymoo/algorithms/moo/dnsga2.py +76 -0
- pymoo/algorithms/moo/kgb.py +446 -0
- pymoo/algorithms/moo/moead.py +183 -0
- pymoo/algorithms/moo/nsga2.py +113 -0
- pymoo/algorithms/moo/nsga3.py +358 -0
- pymoo/algorithms/moo/pinsga2.py +370 -0
- pymoo/algorithms/moo/rnsga2.py +188 -0
- pymoo/algorithms/moo/rnsga3.py +246 -0
- pymoo/algorithms/moo/rvea.py +214 -0
- pymoo/algorithms/moo/sms.py +195 -0
- pymoo/algorithms/moo/spea2.py +190 -0
- pymoo/algorithms/moo/unsga3.py +47 -0
- pymoo/algorithms/soo/__init__.py +0 -0
- pymoo/algorithms/soo/convex/__init__.py +0 -0
- pymoo/algorithms/soo/nonconvex/__init__.py +0 -0
- pymoo/algorithms/soo/nonconvex/brkga.py +161 -0
- pymoo/algorithms/soo/nonconvex/cmaes.py +554 -0
- pymoo/algorithms/soo/nonconvex/de.py +279 -0
- pymoo/algorithms/soo/nonconvex/direct.py +149 -0
- pymoo/algorithms/soo/nonconvex/es.py +203 -0
- pymoo/algorithms/soo/nonconvex/g3pcx.py +94 -0
- pymoo/algorithms/soo/nonconvex/ga.py +93 -0
- pymoo/algorithms/soo/nonconvex/ga_niching.py +223 -0
- pymoo/algorithms/soo/nonconvex/isres.py +74 -0
- pymoo/algorithms/soo/nonconvex/nelder.py +251 -0
- pymoo/algorithms/soo/nonconvex/optuna.py +80 -0
- pymoo/algorithms/soo/nonconvex/pattern.py +183 -0
- pymoo/algorithms/soo/nonconvex/pso.py +399 -0
- pymoo/algorithms/soo/nonconvex/pso_ep.py +297 -0
- pymoo/algorithms/soo/nonconvex/random_search.py +25 -0
- pymoo/algorithms/soo/nonconvex/sres.py +56 -0
- pymoo/algorithms/soo/univariate/__init__.py +0 -0
- pymoo/algorithms/soo/univariate/backtracking.py +59 -0
- pymoo/algorithms/soo/univariate/exp.py +46 -0
- pymoo/algorithms/soo/univariate/golden.py +65 -0
- pymoo/algorithms/soo/univariate/quadr_interp.py +81 -0
- pymoo/algorithms/soo/univariate/wolfe.py +163 -0
- pymoo/config.py +33 -0
- pymoo/constraints/__init__.py +3 -0
- pymoo/constraints/adaptive.py +62 -0
- pymoo/constraints/as_obj.py +56 -0
- pymoo/constraints/as_penalty.py +41 -0
- pymoo/constraints/eps.py +26 -0
- pymoo/constraints/from_bounds.py +36 -0
- pymoo/core/__init__.py +0 -0
- pymoo/core/algorithm.py +394 -0
- pymoo/core/callback.py +38 -0
- pymoo/core/crossover.py +77 -0
- pymoo/core/decision_making.py +102 -0
- pymoo/core/decomposition.py +76 -0
- pymoo/core/duplicate.py +163 -0
- pymoo/core/evaluator.py +116 -0
- pymoo/core/indicator.py +34 -0
- pymoo/core/individual.py +784 -0
- pymoo/core/infill.py +64 -0
- pymoo/core/initialization.py +42 -0
- pymoo/core/mating.py +39 -0
- pymoo/core/meta.py +21 -0
- pymoo/core/mixed.py +165 -0
- pymoo/core/mutation.py +44 -0
- pymoo/core/operator.py +40 -0
- pymoo/core/parameters.py +134 -0
- pymoo/core/plot.py +210 -0
- pymoo/core/population.py +180 -0
- pymoo/core/problem.py +460 -0
- pymoo/core/recorder.py +99 -0
- pymoo/core/repair.py +23 -0
- pymoo/core/replacement.py +96 -0
- pymoo/core/result.py +52 -0
- pymoo/core/sampling.py +43 -0
- pymoo/core/selection.py +61 -0
- pymoo/core/solution.py +10 -0
- pymoo/core/survival.py +103 -0
- pymoo/core/termination.py +70 -0
- pymoo/core/variable.py +399 -0
- pymoo/cython/__init__.py +0 -0
- pymoo/cython/calc_perpendicular_distance.cpython-312-x86_64-linux-gnu.so +0 -0
- pymoo/cython/calc_perpendicular_distance.pyx +67 -0
- pymoo/cython/decomposition.cpython-312-x86_64-linux-gnu.so +0 -0
- pymoo/cython/decomposition.pyx +165 -0
- pymoo/cython/hv.cpython-312-x86_64-linux-gnu.so +0 -0
- pymoo/cython/hv.pyx +18 -0
- pymoo/cython/info.cpython-312-x86_64-linux-gnu.so +0 -0
- pymoo/cython/info.pyx +5 -0
- pymoo/cython/mnn.cpython-312-x86_64-linux-gnu.so +0 -0
- pymoo/cython/mnn.pyx +273 -0
- pymoo/cython/non_dominated_sorting.cpython-312-x86_64-linux-gnu.so +0 -0
- pymoo/cython/non_dominated_sorting.pyx +645 -0
- pymoo/cython/pruning_cd.cpython-312-x86_64-linux-gnu.so +0 -0
- pymoo/cython/pruning_cd.pyx +197 -0
- pymoo/cython/stochastic_ranking.cpython-312-x86_64-linux-gnu.so +0 -0
- pymoo/cython/stochastic_ranking.pyx +49 -0
- pymoo/cython/utils.pxd +129 -0
- pymoo/cython/vendor/__init__.py +0 -0
- pymoo/cython/vendor/hypervolume.cpp +1621 -0
- pymoo/cython/vendor/hypervolume.h +63 -0
- pymoo/decomposition/__init__.py +0 -0
- pymoo/decomposition/aasf.py +24 -0
- pymoo/decomposition/asf.py +10 -0
- pymoo/decomposition/pbi.py +13 -0
- pymoo/decomposition/perp_dist.py +13 -0
- pymoo/decomposition/tchebicheff.py +11 -0
- pymoo/decomposition/util.py +13 -0
- pymoo/decomposition/weighted_sum.py +8 -0
- pymoo/docs.py +187 -0
- pymoo/experimental/__init__.py +0 -0
- pymoo/experimental/algorithms/__init__.py +0 -0
- pymoo/experimental/algorithms/gde3.py +57 -0
- pymoo/gradient/__init__.py +21 -0
- pymoo/gradient/automatic.py +57 -0
- pymoo/gradient/grad_autograd.py +105 -0
- pymoo/gradient/grad_complex.py +35 -0
- pymoo/gradient/grad_jax.py +51 -0
- pymoo/gradient/toolbox/__init__.py +6 -0
- pymoo/indicators/__init__.py +0 -0
- pymoo/indicators/distance_indicator.py +55 -0
- pymoo/indicators/gd.py +7 -0
- pymoo/indicators/gd_plus.py +7 -0
- pymoo/indicators/hv/__init__.py +63 -0
- pymoo/indicators/hv/exact.py +71 -0
- pymoo/indicators/hv/exact_2d.py +102 -0
- pymoo/indicators/hv/monte_carlo.py +74 -0
- pymoo/indicators/igd.py +7 -0
- pymoo/indicators/igd_plus.py +7 -0
- pymoo/indicators/kktpm.py +151 -0
- pymoo/indicators/migd.py +55 -0
- pymoo/indicators/rmetric.py +203 -0
- pymoo/indicators/spacing.py +52 -0
- pymoo/mcdm/__init__.py +0 -0
- pymoo/mcdm/compromise_programming.py +19 -0
- pymoo/mcdm/high_tradeoff.py +40 -0
- pymoo/mcdm/pseudo_weights.py +32 -0
- pymoo/operators/__init__.py +0 -0
- pymoo/operators/control.py +187 -0
- pymoo/operators/crossover/__init__.py +0 -0
- pymoo/operators/crossover/binx.py +45 -0
- pymoo/operators/crossover/dex.py +122 -0
- pymoo/operators/crossover/erx.py +162 -0
- pymoo/operators/crossover/expx.py +51 -0
- pymoo/operators/crossover/hux.py +37 -0
- pymoo/operators/crossover/nox.py +13 -0
- pymoo/operators/crossover/ox.py +84 -0
- pymoo/operators/crossover/pcx.py +82 -0
- pymoo/operators/crossover/pntx.py +49 -0
- pymoo/operators/crossover/sbx.py +125 -0
- pymoo/operators/crossover/spx.py +5 -0
- pymoo/operators/crossover/ux.py +20 -0
- pymoo/operators/mutation/__init__.py +0 -0
- pymoo/operators/mutation/bitflip.py +17 -0
- pymoo/operators/mutation/gauss.py +58 -0
- pymoo/operators/mutation/inversion.py +42 -0
- pymoo/operators/mutation/nom.py +7 -0
- pymoo/operators/mutation/pm.py +94 -0
- pymoo/operators/mutation/rm.py +23 -0
- pymoo/operators/repair/__init__.py +0 -0
- pymoo/operators/repair/bounce_back.py +32 -0
- pymoo/operators/repair/bounds_repair.py +95 -0
- pymoo/operators/repair/inverse_penalty.py +89 -0
- pymoo/operators/repair/rounding.py +18 -0
- pymoo/operators/repair/to_bound.py +31 -0
- pymoo/operators/repair/vtype.py +11 -0
- pymoo/operators/sampling/__init__.py +0 -0
- pymoo/operators/sampling/lhs.py +73 -0
- pymoo/operators/sampling/rnd.py +50 -0
- pymoo/operators/selection/__init__.py +0 -0
- pymoo/operators/selection/rnd.py +72 -0
- pymoo/operators/selection/tournament.py +76 -0
- pymoo/operators/survival/__init__.py +0 -0
- pymoo/operators/survival/rank_and_crowding/__init__.py +1 -0
- pymoo/operators/survival/rank_and_crowding/classes.py +209 -0
- pymoo/operators/survival/rank_and_crowding/metrics.py +208 -0
- pymoo/optimize.py +72 -0
- pymoo/problems/__init__.py +157 -0
- pymoo/problems/dyn.py +47 -0
- pymoo/problems/dynamic/__init__.py +0 -0
- pymoo/problems/dynamic/cec2015.py +108 -0
- pymoo/problems/dynamic/df.py +452 -0
- pymoo/problems/dynamic/misc.py +167 -0
- pymoo/problems/functional.py +48 -0
- pymoo/problems/many/__init__.py +5 -0
- pymoo/problems/many/cdtlz.py +159 -0
- pymoo/problems/many/dcdtlz.py +88 -0
- pymoo/problems/many/dtlz.py +264 -0
- pymoo/problems/many/wfg.py +550 -0
- pymoo/problems/multi/__init__.py +14 -0
- pymoo/problems/multi/bnh.py +34 -0
- pymoo/problems/multi/carside.py +48 -0
- pymoo/problems/multi/clutch.py +104 -0
- pymoo/problems/multi/csi.py +55 -0
- pymoo/problems/multi/ctp.py +198 -0
- pymoo/problems/multi/dascmop.py +213 -0
- pymoo/problems/multi/kursawe.py +25 -0
- pymoo/problems/multi/modact.py +68 -0
- pymoo/problems/multi/mw.py +400 -0
- pymoo/problems/multi/omnitest.py +48 -0
- pymoo/problems/multi/osy.py +32 -0
- pymoo/problems/multi/srn.py +28 -0
- pymoo/problems/multi/sympart.py +94 -0
- pymoo/problems/multi/tnk.py +24 -0
- pymoo/problems/multi/truss2d.py +83 -0
- pymoo/problems/multi/welded_beam.py +41 -0
- pymoo/problems/multi/wrm.py +36 -0
- pymoo/problems/multi/zdt.py +151 -0
- pymoo/problems/multi_to_single.py +22 -0
- pymoo/problems/single/__init__.py +12 -0
- pymoo/problems/single/ackley.py +24 -0
- pymoo/problems/single/cantilevered_beam.py +34 -0
- pymoo/problems/single/flowshop_scheduling.py +112 -0
- pymoo/problems/single/g.py +874 -0
- pymoo/problems/single/griewank.py +18 -0
- pymoo/problems/single/himmelblau.py +15 -0
- pymoo/problems/single/knapsack.py +48 -0
- pymoo/problems/single/mopta08.py +26 -0
- pymoo/problems/single/multimodal.py +20 -0
- pymoo/problems/single/pressure_vessel.py +30 -0
- pymoo/problems/single/rastrigin.py +20 -0
- pymoo/problems/single/rosenbrock.py +22 -0
- pymoo/problems/single/schwefel.py +18 -0
- pymoo/problems/single/simple.py +13 -0
- pymoo/problems/single/sphere.py +19 -0
- pymoo/problems/single/traveling_salesman.py +79 -0
- pymoo/problems/single/zakharov.py +19 -0
- pymoo/problems/static.py +14 -0
- pymoo/problems/util.py +42 -0
- pymoo/problems/zero_to_one.py +27 -0
- pymoo/termination/__init__.py +23 -0
- pymoo/termination/collection.py +12 -0
- pymoo/termination/cv.py +48 -0
- pymoo/termination/default.py +45 -0
- pymoo/termination/delta.py +64 -0
- pymoo/termination/fmin.py +16 -0
- pymoo/termination/ftol.py +144 -0
- pymoo/termination/indicator.py +49 -0
- pymoo/termination/max_eval.py +14 -0
- pymoo/termination/max_gen.py +15 -0
- pymoo/termination/max_time.py +20 -0
- pymoo/termination/robust.py +34 -0
- pymoo/termination/xtol.py +33 -0
- pymoo/util/__init__.py +0 -0
- pymoo/util/archive.py +150 -0
- pymoo/util/cache.py +29 -0
- pymoo/util/clearing.py +82 -0
- pymoo/util/display/__init__.py +0 -0
- pymoo/util/display/column.py +52 -0
- pymoo/util/display/display.py +34 -0
- pymoo/util/display/multi.py +96 -0
- pymoo/util/display/output.py +53 -0
- pymoo/util/display/progress.py +54 -0
- pymoo/util/display/single.py +67 -0
- pymoo/util/dominator.py +67 -0
- pymoo/util/function_loader.py +129 -0
- pymoo/util/hv.py +23 -0
- pymoo/util/matlab_engine.py +39 -0
- pymoo/util/misc.py +460 -0
- pymoo/util/mnn.py +70 -0
- pymoo/util/nds/__init__.py +0 -0
- pymoo/util/nds/dominance_degree_non_dominated_sort.py +159 -0
- pymoo/util/nds/efficient_non_dominated_sort.py +152 -0
- pymoo/util/nds/fast_non_dominated_sort.py +70 -0
- pymoo/util/nds/naive_non_dominated_sort.py +36 -0
- pymoo/util/nds/non_dominated_sorting.py +67 -0
- pymoo/util/nds/tree_based_non_dominated_sort.py +133 -0
- pymoo/util/normalization.py +312 -0
- pymoo/util/optimum.py +42 -0
- pymoo/util/plotting.py +177 -0
- pymoo/util/pruning_cd.py +89 -0
- pymoo/util/randomized_argsort.py +60 -0
- pymoo/util/ref_dirs/__init__.py +24 -0
- pymoo/util/ref_dirs/construction.py +88 -0
- pymoo/util/ref_dirs/das_dennis.py +52 -0
- pymoo/util/ref_dirs/energy.py +319 -0
- pymoo/util/ref_dirs/energy_layer.py +119 -0
- pymoo/util/ref_dirs/genetic_algorithm.py +63 -0
- pymoo/util/ref_dirs/incremental.py +68 -0
- pymoo/util/ref_dirs/misc.py +128 -0
- pymoo/util/ref_dirs/optimizer.py +59 -0
- pymoo/util/ref_dirs/performance.py +162 -0
- pymoo/util/ref_dirs/reduction.py +85 -0
- pymoo/util/ref_dirs/sample_and_map.py +24 -0
- pymoo/util/reference_direction.py +260 -0
- pymoo/util/remote.py +55 -0
- pymoo/util/roulette.py +27 -0
- pymoo/util/running_metric.py +128 -0
- pymoo/util/sliding_window.py +25 -0
- pymoo/util/stochastic_ranking.py +32 -0
- pymoo/util/value_functions.py +719 -0
- pymoo/util/vectors.py +40 -0
- pymoo/util/vf_dominator.py +99 -0
- pymoo/vendor/__init__.py +0 -0
- pymoo/vendor/cec2018.py +398 -0
- pymoo/vendor/gta.py +617 -0
- pymoo/vendor/hv.py +267 -0
- pymoo/vendor/vendor_cmaes.py +412 -0
- pymoo/vendor/vendor_coco.py +81 -0
- pymoo/vendor/vendor_scipy.py +232 -0
- pymoo/version.py +1 -0
- pymoo/visualization/__init__.py +8 -0
- pymoo/visualization/fitness_landscape.py +127 -0
- pymoo/visualization/heatmap.py +123 -0
- pymoo/visualization/pcp.py +120 -0
- pymoo/visualization/petal.py +91 -0
- pymoo/visualization/radar.py +108 -0
- pymoo/visualization/radviz.py +68 -0
- pymoo/visualization/scatter.py +150 -0
- pymoo/visualization/star_coordinate.py +75 -0
- pymoo/visualization/util.py +123 -0
- pymoo/visualization/video/__init__.py +0 -0
- pymoo/visualization/video/callback_video.py +82 -0
- pymoo/visualization/video/one_var_one_obj.py +57 -0
- pymoo/visualization/video/two_var_one_obj.py +62 -0
- pymoo-0.6.1.5.dev0.dist-info/METADATA +187 -0
- pymoo-0.6.1.5.dev0.dist-info/RECORD +328 -0
- pymoo-0.6.1.5.dev0.dist-info/WHEEL +6 -0
- pymoo-0.6.1.5.dev0.dist-info/licenses/LICENSE +191 -0
- pymoo-0.6.1.5.dev0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import warnings
|
|
3
|
+
|
|
4
|
+
from pymoo.algorithms.base.genetic import GeneticAlgorithm
|
|
5
|
+
from pymoo.docs import parse_doc_string
|
|
6
|
+
from pymoo.operators.crossover.sbx import SBX
|
|
7
|
+
from pymoo.operators.mutation.pm import PM
|
|
8
|
+
from pymoo.operators.survival.rank_and_crowding import RankAndCrowding
|
|
9
|
+
from pymoo.operators.sampling.rnd import FloatRandomSampling
|
|
10
|
+
from pymoo.operators.selection.tournament import compare, TournamentSelection
|
|
11
|
+
from pymoo.termination.default import DefaultMultiObjectiveTermination
|
|
12
|
+
from pymoo.util.display.multi import MultiObjectiveOutput
|
|
13
|
+
from pymoo.util.dominator import Dominator
|
|
14
|
+
from pymoo.util.misc import has_feasible
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
18
|
+
# Binary Tournament Selection Function
|
|
19
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def binary_tournament(pop, P, algorithm, **kwargs):
|
|
23
|
+
n_tournaments, n_parents = P.shape
|
|
24
|
+
|
|
25
|
+
if n_parents != 2:
|
|
26
|
+
raise ValueError("Only implemented for binary tournament!")
|
|
27
|
+
|
|
28
|
+
tournament_type = algorithm.tournament_type
|
|
29
|
+
S = np.full(n_tournaments, np.nan)
|
|
30
|
+
|
|
31
|
+
for i in range(n_tournaments):
|
|
32
|
+
|
|
33
|
+
a, b = P[i, 0], P[i, 1]
|
|
34
|
+
a_cv, a_f, b_cv, b_f = pop[a].CV[0], pop[a].F, pop[b].CV[0], pop[b].F
|
|
35
|
+
rank_a, cd_a = pop[a].get("rank", "crowding")
|
|
36
|
+
rank_b, cd_b = pop[b].get("rank", "crowding")
|
|
37
|
+
|
|
38
|
+
# if at least one solution is infeasible
|
|
39
|
+
if a_cv > 0.0 or b_cv > 0.0:
|
|
40
|
+
S[i] = compare(a, a_cv, b, b_cv, method='smaller_is_better', return_random_if_equal=True)
|
|
41
|
+
|
|
42
|
+
# both solutions are feasible
|
|
43
|
+
else:
|
|
44
|
+
|
|
45
|
+
if tournament_type == 'comp_by_dom_and_crowding':
|
|
46
|
+
rel = Dominator.get_relation(a_f, b_f)
|
|
47
|
+
if rel == 1:
|
|
48
|
+
S[i] = a
|
|
49
|
+
elif rel == -1:
|
|
50
|
+
S[i] = b
|
|
51
|
+
|
|
52
|
+
elif tournament_type == 'comp_by_rank_and_crowding':
|
|
53
|
+
S[i] = compare(a, rank_a, b, rank_b, method='smaller_is_better')
|
|
54
|
+
|
|
55
|
+
else:
|
|
56
|
+
raise Exception("Unknown tournament type.")
|
|
57
|
+
|
|
58
|
+
# if rank or domination relation didn't make a decision compare by crowding
|
|
59
|
+
if np.isnan(S[i]):
|
|
60
|
+
S[i] = compare(a, cd_a, b, cd_b, method='larger_is_better', return_random_if_equal=True)
|
|
61
|
+
|
|
62
|
+
return S[:, None].astype(int, copy=False)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
66
|
+
# Survival Selection
|
|
67
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class RankAndCrowdingSurvival(RankAndCrowding):
|
|
71
|
+
|
|
72
|
+
def __init__(self, nds=None, crowding_func="cd"):
|
|
73
|
+
super().__init__(nds, crowding_func)
|
|
74
|
+
|
|
75
|
+
# =========================================================================================================
|
|
76
|
+
# Implementation
|
|
77
|
+
# =========================================================================================================
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class NSGA2(GeneticAlgorithm):
|
|
81
|
+
|
|
82
|
+
def __init__(self,
|
|
83
|
+
pop_size=100,
|
|
84
|
+
sampling=FloatRandomSampling(),
|
|
85
|
+
selection=TournamentSelection(func_comp=binary_tournament),
|
|
86
|
+
crossover=SBX(eta=15, prob=0.9),
|
|
87
|
+
mutation=PM(eta=20),
|
|
88
|
+
survival=RankAndCrowding(),
|
|
89
|
+
output=MultiObjectiveOutput(),
|
|
90
|
+
**kwargs):
|
|
91
|
+
|
|
92
|
+
super().__init__(
|
|
93
|
+
pop_size=pop_size,
|
|
94
|
+
sampling=sampling,
|
|
95
|
+
selection=selection,
|
|
96
|
+
crossover=crossover,
|
|
97
|
+
mutation=mutation,
|
|
98
|
+
survival=survival,
|
|
99
|
+
output=output,
|
|
100
|
+
advance_after_initial_infill=True,
|
|
101
|
+
**kwargs)
|
|
102
|
+
|
|
103
|
+
self.termination = DefaultMultiObjectiveTermination()
|
|
104
|
+
self.tournament_type = 'comp_by_dom_and_crowding'
|
|
105
|
+
|
|
106
|
+
def _set_optimum(self, **kwargs):
|
|
107
|
+
if not has_feasible(self.pop):
|
|
108
|
+
self.opt = self.pop[[np.argmin(self.pop.get("CV"))]]
|
|
109
|
+
else:
|
|
110
|
+
self.opt = self.pop[self.pop.get("rank") == 0]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
parse_doc_string(NSGA2.__init__)
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from numpy.linalg import LinAlgError
|
|
5
|
+
|
|
6
|
+
from pymoo.algorithms.base.genetic import GeneticAlgorithm
|
|
7
|
+
from pymoo.core.survival import Survival
|
|
8
|
+
from pymoo.docs import parse_doc_string
|
|
9
|
+
from pymoo.operators.crossover.sbx import SBX
|
|
10
|
+
from pymoo.operators.mutation.pm import PM
|
|
11
|
+
from pymoo.operators.sampling.rnd import FloatRandomSampling
|
|
12
|
+
from pymoo.operators.selection.tournament import TournamentSelection, compare
|
|
13
|
+
from pymoo.util.display.multi import MultiObjectiveOutput
|
|
14
|
+
from pymoo.util.function_loader import load_function
|
|
15
|
+
from pymoo.util.misc import intersect, has_feasible
|
|
16
|
+
from pymoo.util.nds.non_dominated_sorting import NonDominatedSorting
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# =========================================================================================================
|
|
20
|
+
# Implementation
|
|
21
|
+
# =========================================================================================================
|
|
22
|
+
|
|
23
|
+
def comp_by_cv_then_random(pop, P, **kwargs):
|
|
24
|
+
S = np.full(P.shape[0], np.nan)
|
|
25
|
+
|
|
26
|
+
for i in range(P.shape[0]):
|
|
27
|
+
a, b = P[i, 0], P[i, 1]
|
|
28
|
+
|
|
29
|
+
# if at least one solution is infeasible
|
|
30
|
+
if pop[a].CV > 0.0 or pop[b].CV > 0.0:
|
|
31
|
+
S[i] = compare(a, pop[a].CV, b, pop[b].CV, method='smaller_is_better', return_random_if_equal=True)
|
|
32
|
+
|
|
33
|
+
# both solutions are feasible just set random
|
|
34
|
+
else:
|
|
35
|
+
S[i] = np.random.choice([a, b])
|
|
36
|
+
|
|
37
|
+
return S[:, None].astype(int)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class NSGA3(GeneticAlgorithm):
|
|
41
|
+
|
|
42
|
+
def __init__(self,
|
|
43
|
+
ref_dirs,
|
|
44
|
+
pop_size=None,
|
|
45
|
+
sampling=FloatRandomSampling(),
|
|
46
|
+
selection=TournamentSelection(func_comp=comp_by_cv_then_random),
|
|
47
|
+
crossover=SBX(eta=30, prob=1.0),
|
|
48
|
+
mutation=PM(eta=20),
|
|
49
|
+
eliminate_duplicates=True,
|
|
50
|
+
n_offsprings=None,
|
|
51
|
+
output=MultiObjectiveOutput(),
|
|
52
|
+
**kwargs):
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
|
|
58
|
+
ref_dirs : {ref_dirs}
|
|
59
|
+
pop_size : int (default = None)
|
|
60
|
+
By default the population size is set to None which means that it will be equal to the number of reference
|
|
61
|
+
line. However, if desired this can be overwritten by providing a positive number.
|
|
62
|
+
sampling : {sampling}
|
|
63
|
+
selection : {selection}
|
|
64
|
+
crossover : {crossover}
|
|
65
|
+
mutation : {mutation}
|
|
66
|
+
eliminate_duplicates : {eliminate_duplicates}
|
|
67
|
+
n_offsprings : {n_offsprings}
|
|
68
|
+
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
self.ref_dirs = ref_dirs
|
|
72
|
+
|
|
73
|
+
# in case of R-NSGA-3 they will be None - otherwise this will be executed
|
|
74
|
+
if self.ref_dirs is not None:
|
|
75
|
+
|
|
76
|
+
if pop_size is None:
|
|
77
|
+
pop_size = len(self.ref_dirs)
|
|
78
|
+
|
|
79
|
+
if pop_size < len(self.ref_dirs):
|
|
80
|
+
print(
|
|
81
|
+
f"WARNING: pop_size={pop_size} is less than the number of reference directions ref_dirs={len(self.ref_dirs)}.\n"
|
|
82
|
+
"This might cause unwanted behavior of the algorithm. \n"
|
|
83
|
+
"Please make sure pop_size is equal or larger than the number of reference directions. ")
|
|
84
|
+
|
|
85
|
+
if 'survival' in kwargs:
|
|
86
|
+
survival = kwargs['survival']
|
|
87
|
+
del kwargs['survival']
|
|
88
|
+
else:
|
|
89
|
+
survival = ReferenceDirectionSurvival(ref_dirs)
|
|
90
|
+
|
|
91
|
+
super().__init__(pop_size=pop_size,
|
|
92
|
+
sampling=sampling,
|
|
93
|
+
selection=selection,
|
|
94
|
+
crossover=crossover,
|
|
95
|
+
mutation=mutation,
|
|
96
|
+
survival=survival,
|
|
97
|
+
eliminate_duplicates=eliminate_duplicates,
|
|
98
|
+
n_offsprings=n_offsprings,
|
|
99
|
+
output=output,
|
|
100
|
+
advance_after_initial_infill=True,
|
|
101
|
+
**kwargs)
|
|
102
|
+
|
|
103
|
+
def _setup(self, problem, **kwargs):
|
|
104
|
+
|
|
105
|
+
if self.ref_dirs is not None:
|
|
106
|
+
if self.ref_dirs.shape[1] != problem.n_obj:
|
|
107
|
+
raise Exception(
|
|
108
|
+
"Dimensionality of reference points must be equal to the number of objectives: %s != %s" %
|
|
109
|
+
(self.ref_dirs.shape[1], problem.n_obj))
|
|
110
|
+
|
|
111
|
+
def _set_optimum(self, **kwargs):
|
|
112
|
+
if not has_feasible(self.pop):
|
|
113
|
+
self.opt = self.pop[[np.argmin(self.pop.get("CV"))]]
|
|
114
|
+
else:
|
|
115
|
+
if len(self.survival.opt):
|
|
116
|
+
self.opt = self.survival.opt
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# =========================================================================================================
|
|
120
|
+
# Survival
|
|
121
|
+
# =========================================================================================================
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class ReferenceDirectionSurvival(Survival):
|
|
125
|
+
|
|
126
|
+
def __init__(self, ref_dirs):
|
|
127
|
+
super().__init__(filter_infeasible=True)
|
|
128
|
+
self.ref_dirs = ref_dirs
|
|
129
|
+
self.opt = None
|
|
130
|
+
self.norm = HyperplaneNormalization(ref_dirs.shape[1])
|
|
131
|
+
|
|
132
|
+
def _do(self, problem, pop, n_survive, D=None, **kwargs):
|
|
133
|
+
|
|
134
|
+
# attributes to be set after the survival
|
|
135
|
+
F = pop.get("F")
|
|
136
|
+
|
|
137
|
+
# calculate the fronts of the population
|
|
138
|
+
fronts, rank = NonDominatedSorting().do(F, return_rank=True, n_stop_if_ranked=n_survive)
|
|
139
|
+
non_dominated, last_front = fronts[0], fronts[-1]
|
|
140
|
+
|
|
141
|
+
# update the hyperplane based boundary estimation
|
|
142
|
+
hyp_norm = self.norm
|
|
143
|
+
hyp_norm.update(F, nds=non_dominated)
|
|
144
|
+
ideal, nadir = hyp_norm.ideal_point, hyp_norm.nadir_point
|
|
145
|
+
|
|
146
|
+
# consider only the population until we come to the splitting front
|
|
147
|
+
I = np.concatenate(fronts)
|
|
148
|
+
pop, rank, F = pop[I], rank[I], F[I]
|
|
149
|
+
|
|
150
|
+
# update the front indices for the current population
|
|
151
|
+
counter = 0
|
|
152
|
+
for i in range(len(fronts)):
|
|
153
|
+
for j in range(len(fronts[i])):
|
|
154
|
+
fronts[i][j] = counter
|
|
155
|
+
counter += 1
|
|
156
|
+
last_front = fronts[-1]
|
|
157
|
+
|
|
158
|
+
# associate individuals to niches
|
|
159
|
+
niche_of_individuals, dist_to_niche, dist_matrix = \
|
|
160
|
+
associate_to_niches(F, self.ref_dirs, ideal, nadir)
|
|
161
|
+
|
|
162
|
+
# attributes of a population
|
|
163
|
+
pop.set('rank', rank,
|
|
164
|
+
'niche', niche_of_individuals,
|
|
165
|
+
'dist_to_niche', dist_to_niche)
|
|
166
|
+
|
|
167
|
+
# set the optimum, first front and closest to all reference directions
|
|
168
|
+
closest = np.unique(dist_matrix[:, np.unique(niche_of_individuals)].argmin(axis=0))
|
|
169
|
+
self.opt = pop[intersect(fronts[0], closest)]
|
|
170
|
+
if len(self.opt) == 0:
|
|
171
|
+
self.opt = pop[fronts[0]]
|
|
172
|
+
|
|
173
|
+
# if we need to select individuals to survive
|
|
174
|
+
if len(pop) > n_survive:
|
|
175
|
+
|
|
176
|
+
# if there is only one front
|
|
177
|
+
if len(fronts) == 1:
|
|
178
|
+
n_remaining = n_survive
|
|
179
|
+
until_last_front = np.array([], dtype=int)
|
|
180
|
+
niche_count = np.zeros(len(self.ref_dirs), dtype=int)
|
|
181
|
+
|
|
182
|
+
# if some individuals already survived
|
|
183
|
+
else:
|
|
184
|
+
until_last_front = np.concatenate(fronts[:-1])
|
|
185
|
+
niche_count = calc_niche_count(len(self.ref_dirs), niche_of_individuals[until_last_front])
|
|
186
|
+
n_remaining = n_survive - len(until_last_front)
|
|
187
|
+
|
|
188
|
+
S = niching(pop[last_front], n_remaining, niche_count, niche_of_individuals[last_front],
|
|
189
|
+
dist_to_niche[last_front])
|
|
190
|
+
|
|
191
|
+
survivors = np.concatenate((until_last_front, last_front[S].tolist()))
|
|
192
|
+
pop = pop[survivors]
|
|
193
|
+
|
|
194
|
+
return pop
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def niching(pop, n_remaining, niche_count, niche_of_individuals, dist_to_niche):
|
|
198
|
+
survivors = []
|
|
199
|
+
|
|
200
|
+
# boolean array of elements that are considered for each iteration
|
|
201
|
+
mask = np.full(len(pop), True)
|
|
202
|
+
|
|
203
|
+
while len(survivors) < n_remaining:
|
|
204
|
+
|
|
205
|
+
# number of individuals to select in this iteration
|
|
206
|
+
n_select = n_remaining - len(survivors)
|
|
207
|
+
|
|
208
|
+
# all niches where new individuals can be assigned to and the corresponding niche count
|
|
209
|
+
next_niches_list = np.unique(niche_of_individuals[mask])
|
|
210
|
+
next_niche_count = niche_count[next_niches_list]
|
|
211
|
+
|
|
212
|
+
# the minimum niche count
|
|
213
|
+
min_niche_count = next_niche_count.min()
|
|
214
|
+
|
|
215
|
+
# all niches with the minimum niche count (truncate randomly if there are more niches than remaining individuals)
|
|
216
|
+
next_niches = next_niches_list[np.where(next_niche_count == min_niche_count)[0]]
|
|
217
|
+
next_niches = next_niches[np.random.permutation(len(next_niches))[:n_select]]
|
|
218
|
+
|
|
219
|
+
for next_niche in next_niches:
|
|
220
|
+
|
|
221
|
+
# indices of individuals that are considered and assign to next_niche
|
|
222
|
+
next_ind = np.where(np.logical_and(niche_of_individuals == next_niche, mask))[0]
|
|
223
|
+
|
|
224
|
+
# shuffle to break random tie (equal perp. dist) or select randomly
|
|
225
|
+
np.random.shuffle(next_ind)
|
|
226
|
+
|
|
227
|
+
if niche_count[next_niche] == 0:
|
|
228
|
+
next_ind = next_ind[np.argmin(dist_to_niche[next_ind])]
|
|
229
|
+
else:
|
|
230
|
+
# already randomized through shuffling
|
|
231
|
+
next_ind = next_ind[0]
|
|
232
|
+
|
|
233
|
+
# add the selected individual to the survivors
|
|
234
|
+
mask[next_ind] = False
|
|
235
|
+
survivors.append(int(next_ind))
|
|
236
|
+
|
|
237
|
+
# increase the corresponding niche count
|
|
238
|
+
niche_count[next_niche] += 1
|
|
239
|
+
|
|
240
|
+
return survivors
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def associate_to_niches(F, niches, ideal_point, nadir_point, utopian_epsilon=0.0):
|
|
244
|
+
utopian_point = ideal_point - utopian_epsilon
|
|
245
|
+
|
|
246
|
+
denom = nadir_point - utopian_point
|
|
247
|
+
denom[denom == 0] = 1e-12
|
|
248
|
+
|
|
249
|
+
# normalize by ideal point and intercepts
|
|
250
|
+
N = (F - utopian_point) / denom
|
|
251
|
+
dist_matrix = load_function("calc_perpendicular_distance")(N, niches)
|
|
252
|
+
|
|
253
|
+
niche_of_individuals = np.argmin(dist_matrix, axis=1)
|
|
254
|
+
dist_to_niche = dist_matrix[np.arange(F.shape[0]), niche_of_individuals]
|
|
255
|
+
|
|
256
|
+
return niche_of_individuals, dist_to_niche, dist_matrix
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def calc_niche_count(n_niches, niche_of_individuals):
|
|
260
|
+
niche_count = np.zeros(n_niches, dtype=int)
|
|
261
|
+
index, count = np.unique(niche_of_individuals, return_counts=True)
|
|
262
|
+
niche_count[index] = count
|
|
263
|
+
return niche_count
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
# =========================================================================================================
|
|
267
|
+
# Normalization
|
|
268
|
+
# =========================================================================================================
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class HyperplaneNormalization:
|
|
272
|
+
|
|
273
|
+
def __init__(self, n_dim) -> None:
|
|
274
|
+
super().__init__()
|
|
275
|
+
self.ideal_point = np.full(n_dim, np.inf)
|
|
276
|
+
self.worst_point = np.full(n_dim, -np.inf)
|
|
277
|
+
self.nadir_point = None
|
|
278
|
+
self.extreme_points = None
|
|
279
|
+
|
|
280
|
+
def update(self, F, nds=None):
|
|
281
|
+
# find or usually update the new ideal point - from feasible solutions
|
|
282
|
+
self.ideal_point = np.min(np.vstack((self.ideal_point, F)), axis=0)
|
|
283
|
+
self.worst_point = np.max(np.vstack((self.worst_point, F)), axis=0)
|
|
284
|
+
|
|
285
|
+
# this decides whether only non-dominated points or all points are used to determine the extreme points
|
|
286
|
+
if nds is None:
|
|
287
|
+
nds = np.arange(len(F))
|
|
288
|
+
|
|
289
|
+
# find the extreme points for normalization
|
|
290
|
+
self.extreme_points = get_extreme_points_c(F[nds, :], self.ideal_point,
|
|
291
|
+
extreme_points=self.extreme_points)
|
|
292
|
+
|
|
293
|
+
# find the intercepts for normalization and do backup if gaussian elimination fails
|
|
294
|
+
worst_of_population = np.max(F, axis=0)
|
|
295
|
+
worst_of_front = np.max(F[nds, :], axis=0)
|
|
296
|
+
|
|
297
|
+
self.nadir_point = get_nadir_point(self.extreme_points, self.ideal_point, self.worst_point,
|
|
298
|
+
worst_of_front, worst_of_population)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def get_extreme_points_c(F, ideal_point, extreme_points=None):
|
|
302
|
+
# calculate the asf which is used for the extreme point decomposition
|
|
303
|
+
weights = np.eye(F.shape[1])
|
|
304
|
+
weights[weights == 0] = 1e6
|
|
305
|
+
|
|
306
|
+
# add the old extreme points to never lose them for normalization
|
|
307
|
+
_F = F
|
|
308
|
+
if extreme_points is not None:
|
|
309
|
+
_F = np.concatenate([extreme_points, _F], axis=0)
|
|
310
|
+
|
|
311
|
+
# use __F because we substitute small values to be 0
|
|
312
|
+
__F = _F - ideal_point
|
|
313
|
+
__F[__F < 1e-3] = 0
|
|
314
|
+
|
|
315
|
+
# update the extreme points for the normalization having the highest asf value each
|
|
316
|
+
F_asf = np.max(__F * weights[:, None, :], axis=2)
|
|
317
|
+
|
|
318
|
+
I = np.argmin(F_asf, axis=1)
|
|
319
|
+
extreme_points = _F[I, :]
|
|
320
|
+
|
|
321
|
+
return extreme_points
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def get_nadir_point(extreme_points, ideal_point, worst_point, worst_of_front, worst_of_population):
|
|
325
|
+
try:
|
|
326
|
+
|
|
327
|
+
# find the intercepts using gaussian elimination
|
|
328
|
+
M = extreme_points - ideal_point
|
|
329
|
+
b = np.ones(extreme_points.shape[1])
|
|
330
|
+
plane = np.linalg.solve(M, b)
|
|
331
|
+
|
|
332
|
+
warnings.simplefilter("ignore")
|
|
333
|
+
intercepts = 1 / plane
|
|
334
|
+
|
|
335
|
+
nadir_point = ideal_point + intercepts
|
|
336
|
+
|
|
337
|
+
# check if the hyperplane makes sense
|
|
338
|
+
if not np.allclose(np.dot(M, plane), b) or np.any(intercepts <= 1e-6):
|
|
339
|
+
raise LinAlgError()
|
|
340
|
+
|
|
341
|
+
# if the nadir point should be larger than any value discovered so far set it to that value
|
|
342
|
+
# NOTE: different to the proposed version in the paper
|
|
343
|
+
b = nadir_point > worst_point
|
|
344
|
+
nadir_point[b] = worst_point[b]
|
|
345
|
+
|
|
346
|
+
except LinAlgError:
|
|
347
|
+
|
|
348
|
+
# fall back to worst of front otherwise
|
|
349
|
+
nadir_point = worst_of_front
|
|
350
|
+
|
|
351
|
+
# if the range is too small set it to worst of population
|
|
352
|
+
b = nadir_point - ideal_point <= 1e-6
|
|
353
|
+
nadir_point[b] = worst_of_population[b]
|
|
354
|
+
|
|
355
|
+
return nadir_point
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
parse_doc_string(NSGA3.__init__)
|