pymoo 0.6.1.6__cp312-cp312-macosx_10_13_universal2.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.
- 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 +110 -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 +91 -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/cmopso.py +239 -0
- pymoo/algorithms/moo/ctaea.py +305 -0
- pymoo/algorithms/moo/dnsga2.py +80 -0
- pymoo/algorithms/moo/kgb.py +450 -0
- pymoo/algorithms/moo/moead.py +183 -0
- pymoo/algorithms/moo/mopso_cd.py +309 -0
- pymoo/algorithms/moo/nsga2.py +113 -0
- pymoo/algorithms/moo/nsga3.py +361 -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 +196 -0
- pymoo/algorithms/moo/spea2.py +191 -0
- pymoo/algorithms/moo/unsga3.py +49 -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 +162 -0
- pymoo/algorithms/soo/nonconvex/cmaes.py +556 -0
- pymoo/algorithms/soo/nonconvex/de.py +283 -0
- pymoo/algorithms/soo/nonconvex/direct.py +148 -0
- pymoo/algorithms/soo/nonconvex/es.py +213 -0
- pymoo/algorithms/soo/nonconvex/g3pcx.py +94 -0
- pymoo/algorithms/soo/nonconvex/ga.py +95 -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/nrbo.py +191 -0
- pymoo/algorithms/soo/nonconvex/optuna.py +80 -0
- pymoo/algorithms/soo/nonconvex/pattern.py +185 -0
- pymoo/algorithms/soo/nonconvex/pso.py +337 -0
- pymoo/algorithms/soo/nonconvex/pso_ep.py +307 -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/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 +66 -0
- pymoo/constraints/as_obj.py +56 -0
- pymoo/constraints/as_penalty.py +41 -0
- pymoo/constraints/eps.py +34 -0
- pymoo/constraints/from_bounds.py +36 -0
- pymoo/core/__init__.py +0 -0
- pymoo/core/algorithm.py +408 -0
- pymoo/core/callback.py +38 -0
- pymoo/core/crossover.py +79 -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 +65 -0
- pymoo/core/initialization.py +44 -0
- pymoo/core/mating.py +39 -0
- pymoo/core/meta.py +21 -0
- pymoo/core/mixed.py +164 -0
- pymoo/core/mutation.py +44 -0
- pymoo/core/operator.py +46 -0
- pymoo/core/parameters.py +134 -0
- pymoo/core/plot.py +208 -0
- pymoo/core/population.py +180 -0
- pymoo/core/problem.py +373 -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 +45 -0
- pymoo/core/selection.py +61 -0
- pymoo/core/solution.py +10 -0
- pymoo/core/survival.py +107 -0
- pymoo/core/termination.py +70 -0
- pymoo/core/variable.py +415 -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/functions/__init__.py +135 -0
- pymoo/functions/compiled/__init__.py +0 -0
- pymoo/functions/compiled/calc_perpendicular_distance.cpp +27464 -0
- pymoo/functions/compiled/calc_perpendicular_distance.cpython-312-darwin.so +0 -0
- pymoo/functions/compiled/decomposition.cpp +28853 -0
- pymoo/functions/compiled/decomposition.cpython-312-darwin.so +0 -0
- pymoo/functions/compiled/info.cpp +7058 -0
- pymoo/functions/compiled/info.cpython-312-darwin.so +0 -0
- pymoo/functions/compiled/mnn.cpp +30095 -0
- pymoo/functions/compiled/mnn.cpython-312-darwin.so +0 -0
- pymoo/functions/compiled/non_dominated_sorting.cpp +35692 -0
- pymoo/functions/compiled/non_dominated_sorting.cpython-312-darwin.so +0 -0
- pymoo/functions/compiled/pruning_cd.cpp +29248 -0
- pymoo/functions/compiled/pruning_cd.cpython-312-darwin.so +0 -0
- pymoo/functions/compiled/stochastic_ranking.cpp +28042 -0
- pymoo/functions/compiled/stochastic_ranking.cpython-312-darwin.so +0 -0
- pymoo/functions/standard/__init__.py +1 -0
- pymoo/functions/standard/calc_perpendicular_distance.py +20 -0
- pymoo/functions/standard/decomposition.py +18 -0
- pymoo/functions/standard/hv.py +5 -0
- pymoo/functions/standard/mnn.py +78 -0
- pymoo/functions/standard/non_dominated_sorting.py +474 -0
- pymoo/functions/standard/pruning_cd.py +93 -0
- pymoo/functions/standard/stochastic_ranking.py +42 -0
- pymoo/gradient/__init__.py +24 -0
- pymoo/gradient/automatic.py +85 -0
- pymoo/gradient/grad_autograd.py +105 -0
- pymoo/gradient/grad_complex.py +35 -0
- pymoo/gradient/grad_jax.py +51 -0
- pymoo/gradient/numpy.py +22 -0
- pymoo/gradient/toolbox/__init__.py +19 -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 +59 -0
- pymoo/indicators/hv/approximate.py +105 -0
- pymoo/indicators/hv/exact.py +68 -0
- pymoo/indicators/hv/exact_2d.py +102 -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 +190 -0
- pymoo/operators/crossover/__init__.py +0 -0
- pymoo/operators/crossover/binx.py +47 -0
- pymoo/operators/crossover/dex.py +125 -0
- pymoo/operators/crossover/erx.py +164 -0
- pymoo/operators/crossover/expx.py +53 -0
- pymoo/operators/crossover/hux.py +37 -0
- pymoo/operators/crossover/nox.py +25 -0
- pymoo/operators/crossover/ox.py +88 -0
- pymoo/operators/crossover/pcx.py +84 -0
- pymoo/operators/crossover/pntx.py +49 -0
- pymoo/operators/crossover/sbx.py +137 -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 +60 -0
- pymoo/operators/mutation/inversion.py +42 -0
- pymoo/operators/mutation/nom.py +7 -0
- pymoo/operators/mutation/pm.py +96 -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 +97 -0
- pymoo/operators/repair/inverse_penalty.py +91 -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 +76 -0
- pymoo/operators/sampling/rnd.py +52 -0
- pymoo/operators/selection/__init__.py +0 -0
- pymoo/operators/selection/rnd.py +75 -0
- pymoo/operators/selection/tournament.py +78 -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 +212 -0
- pymoo/operators/survival/rank_and_crowding/metrics.py +208 -0
- pymoo/optimize.py +72 -0
- pymoo/parallelization/__init__.py +15 -0
- pymoo/parallelization/dask.py +25 -0
- pymoo/parallelization/joblib.py +28 -0
- pymoo/parallelization/ray.py +31 -0
- pymoo/parallelization/starmap.py +24 -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 +451 -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 +553 -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 +113 -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 +49 -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 +33 -0
- pymoo/util/archive.py +152 -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 +100 -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/hv.py +21 -0
- pymoo/util/matlab_engine.py +39 -0
- pymoo/util/misc.py +447 -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/find_non_dominated.py +54 -0
- pymoo/util/nds/naive_non_dominated_sort.py +36 -0
- pymoo/util/nds/non_dominated_sorting.py +94 -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/randomized_argsort.py +63 -0
- pymoo/util/ref_dirs/__init__.py +24 -0
- pymoo/util/ref_dirs/construction.py +89 -0
- pymoo/util/ref_dirs/das_dennis.py +52 -0
- pymoo/util/ref_dirs/energy.py +317 -0
- pymoo/util/ref_dirs/energy_layer.py +119 -0
- pymoo/util/ref_dirs/genetic_algorithm.py +64 -0
- pymoo/util/ref_dirs/incremental.py +69 -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 +258 -0
- pymoo/util/remote.py +55 -0
- pymoo/util/roulette.py +29 -0
- pymoo/util/running_metric.py +128 -0
- pymoo/util/sliding_window.py +25 -0
- pymoo/util/value_functions.py +720 -0
- pymoo/util/vectors.py +40 -0
- pymoo/util/vf_dominator.py +102 -0
- pymoo/vendor/__init__.py +0 -0
- pymoo/vendor/cec2018.py +398 -0
- pymoo/vendor/gta.py +617 -0
- pymoo/vendor/vendor_cmaes.py +421 -0
- pymoo/vendor/vendor_coco.py +81 -0
- pymoo/vendor/vendor_scipy.py +232 -0
- pymoo/version.py +1 -0
- pymoo/visualization/__init__.py +21 -0
- pymoo/visualization/app/__init__.py +0 -0
- pymoo/visualization/app/pso.py +61 -0
- pymoo/visualization/fitness_landscape.py +128 -0
- pymoo/visualization/heatmap.py +123 -0
- pymoo/visualization/matplotlib.py +61 -0
- pymoo/visualization/pcp.py +121 -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 +296 -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.6.dist-info/METADATA +209 -0
- pymoo-0.6.1.6.dist-info/RECORD +337 -0
- pymoo-0.6.1.6.dist-info/WHEEL +6 -0
- pymoo-0.6.1.6.dist-info/licenses/LICENSE +191 -0
- pymoo-0.6.1.6.dist-info/top_level.txt +1 -0
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Standard (Python) function implementations
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Standard Python implementation of perpendicular distance calculation.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def calc_perpendicular_distance(N, ref_dirs):
|
|
9
|
+
"""Calculate perpendicular distance from points to reference directions."""
|
|
10
|
+
u = np.tile(ref_dirs, (len(N), 1))
|
|
11
|
+
v = np.repeat(N, len(ref_dirs), axis=0)
|
|
12
|
+
|
|
13
|
+
norm_u = np.linalg.norm(u, axis=1)
|
|
14
|
+
|
|
15
|
+
scalar_proj = np.sum(v * u, axis=1) / norm_u
|
|
16
|
+
proj = scalar_proj[:, None] * u / norm_u[:, None]
|
|
17
|
+
val = np.linalg.norm(proj - v, axis=1)
|
|
18
|
+
matrix = np.reshape(val, (len(N), len(ref_dirs)))
|
|
19
|
+
|
|
20
|
+
return matrix
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Standard Python implementations of decomposition functions.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def calc_distance_to_weights(F, weights, utopian_point=None):
|
|
9
|
+
"""Calculate distance to weights for decomposition methods."""
|
|
10
|
+
norm = np.linalg.norm(weights, axis=1)
|
|
11
|
+
|
|
12
|
+
if utopian_point is not None:
|
|
13
|
+
F = F - utopian_point
|
|
14
|
+
|
|
15
|
+
d1 = (F * weights).sum(axis=1) / norm
|
|
16
|
+
d2 = np.linalg.norm(F - (d1[:, None] * weights / norm[:, None]), axis=1)
|
|
17
|
+
|
|
18
|
+
return d1, d2
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Standard Python implementation of M-nearest neighbor calculations.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from scipy.spatial.distance import pdist, squareform
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def calc_mnn(X, n_remove=0):
|
|
10
|
+
"""Calculate M-nearest neighbor distances."""
|
|
11
|
+
return calc_mnn_base(X, n_remove=n_remove, twonn=False)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def calc_2nn(X, n_remove=0):
|
|
15
|
+
"""Calculate 2-nearest neighbor distances."""
|
|
16
|
+
return calc_mnn_base(X, n_remove=n_remove, twonn=True)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def calc_mnn_base(X, n_remove=0, twonn=False):
|
|
20
|
+
"""Base function for M-nearest neighbor calculations."""
|
|
21
|
+
N = X.shape[0]
|
|
22
|
+
M = X.shape[1]
|
|
23
|
+
|
|
24
|
+
if N <= M:
|
|
25
|
+
return np.full(N, np.inf)
|
|
26
|
+
|
|
27
|
+
if n_remove <= (N - M):
|
|
28
|
+
if n_remove < 0:
|
|
29
|
+
n_remove = 0
|
|
30
|
+
else:
|
|
31
|
+
pass
|
|
32
|
+
else:
|
|
33
|
+
n_remove = N - M
|
|
34
|
+
|
|
35
|
+
if twonn:
|
|
36
|
+
M = 2
|
|
37
|
+
|
|
38
|
+
extremes_min = np.argmin(X, axis=0)
|
|
39
|
+
extremes_max = np.argmax(X, axis=0)
|
|
40
|
+
|
|
41
|
+
min_vals = np.min(X, axis=0)
|
|
42
|
+
max_vals = np.max(X, axis=0)
|
|
43
|
+
|
|
44
|
+
extremes = np.concatenate((extremes_min, extremes_max))
|
|
45
|
+
|
|
46
|
+
X = (X - min_vals) / (max_vals - min_vals)
|
|
47
|
+
|
|
48
|
+
H = np.arange(N)
|
|
49
|
+
|
|
50
|
+
D = squareform(pdist(X, metric="sqeuclidean"))
|
|
51
|
+
Dnn = np.partition(D, range(1, M+1), axis=1)[:, 1:M+1]
|
|
52
|
+
d = np.prod(Dnn, axis=1)
|
|
53
|
+
d[extremes] = np.inf
|
|
54
|
+
|
|
55
|
+
n_removed = 0
|
|
56
|
+
|
|
57
|
+
# While n_remove not achieved
|
|
58
|
+
while n_removed < (n_remove - 1):
|
|
59
|
+
|
|
60
|
+
# Obtain element to drop
|
|
61
|
+
_d = d[H]
|
|
62
|
+
_k = np.argmin(_d)
|
|
63
|
+
k = H[_k]
|
|
64
|
+
H = H[H != k]
|
|
65
|
+
|
|
66
|
+
# Update index
|
|
67
|
+
n_removed = n_removed + 1
|
|
68
|
+
if n_removed == n_remove:
|
|
69
|
+
break
|
|
70
|
+
|
|
71
|
+
else:
|
|
72
|
+
|
|
73
|
+
D[:, k] = np.inf
|
|
74
|
+
Dnn[H] = np.partition(D[H], range(1, M+1), axis=1)[:, 1:M+1]
|
|
75
|
+
d[H] = np.prod(Dnn[H], axis=1)
|
|
76
|
+
d[extremes] = np.inf
|
|
77
|
+
|
|
78
|
+
return d
|
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Standard Python implementations of non-dominated sorting algorithms.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from math import floor
|
|
7
|
+
import weakref
|
|
8
|
+
from typing import Literal, List
|
|
9
|
+
|
|
10
|
+
from pymoo.util.dominator import Dominator
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def fast_non_dominated_sort(F, dominator=Dominator(), native_biobj_sorting=False, **kwargs):
|
|
14
|
+
"""Fast non-dominated sorting algorithm.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
F : np.ndarray
|
|
19
|
+
Objective values for each individual.
|
|
20
|
+
dominator : Dominator
|
|
21
|
+
Dominator object for custom dominance relations.
|
|
22
|
+
native_biobj_sorting : bool
|
|
23
|
+
If True, use specialized O(N log N) algorithm for bi-objective problems.
|
|
24
|
+
Default is False.
|
|
25
|
+
"""
|
|
26
|
+
if F.size == 0:
|
|
27
|
+
return []
|
|
28
|
+
|
|
29
|
+
n_points, n_objectives = F.shape
|
|
30
|
+
|
|
31
|
+
# For single objective or single point, return immediately
|
|
32
|
+
if n_points <= 1:
|
|
33
|
+
return [list(range(n_points))] if n_points == 1 else []
|
|
34
|
+
|
|
35
|
+
# For bi-objective problems, optionally use specialized O(N log N) algorithm
|
|
36
|
+
if native_biobj_sorting and n_objectives == 2:
|
|
37
|
+
return _fast_biobjective_nondominated_sort(F)
|
|
38
|
+
|
|
39
|
+
if "dominator" in kwargs:
|
|
40
|
+
M = Dominator.calc_domination_matrix(F)
|
|
41
|
+
else:
|
|
42
|
+
M = dominator.calc_domination_matrix(F)
|
|
43
|
+
|
|
44
|
+
# calculate the dominance matrix
|
|
45
|
+
n = M.shape[0]
|
|
46
|
+
|
|
47
|
+
fronts = []
|
|
48
|
+
|
|
49
|
+
if n == 0:
|
|
50
|
+
return fronts
|
|
51
|
+
|
|
52
|
+
# final rank that will be returned
|
|
53
|
+
n_ranked = 0
|
|
54
|
+
ranked = np.zeros(n, dtype=int)
|
|
55
|
+
|
|
56
|
+
# for each individual a list of all individuals that are dominated by this one
|
|
57
|
+
is_dominating = [[] for _ in range(n)]
|
|
58
|
+
|
|
59
|
+
# storage for the number of solutions dominated this one
|
|
60
|
+
n_dominated = np.zeros(n)
|
|
61
|
+
|
|
62
|
+
current_front = []
|
|
63
|
+
|
|
64
|
+
for i in range(n):
|
|
65
|
+
for j in range(i + 1, n):
|
|
66
|
+
rel = M[i, j]
|
|
67
|
+
if rel == 1:
|
|
68
|
+
is_dominating[i].append(j)
|
|
69
|
+
n_dominated[j] += 1
|
|
70
|
+
elif rel == -1:
|
|
71
|
+
is_dominating[j].append(i)
|
|
72
|
+
n_dominated[i] += 1
|
|
73
|
+
|
|
74
|
+
if n_dominated[i] == 0:
|
|
75
|
+
current_front.append(i)
|
|
76
|
+
ranked[i] = 1.0
|
|
77
|
+
n_ranked += 1
|
|
78
|
+
|
|
79
|
+
# append the first front to the current front
|
|
80
|
+
fronts.append(current_front)
|
|
81
|
+
|
|
82
|
+
# while not all solutions are assigned to a pareto front
|
|
83
|
+
while n_ranked < n:
|
|
84
|
+
next_front = []
|
|
85
|
+
|
|
86
|
+
# for each individual in the current front
|
|
87
|
+
for i in current_front:
|
|
88
|
+
# all solutions that are dominated by this individuals
|
|
89
|
+
for j in is_dominating[i]:
|
|
90
|
+
n_dominated[j] -= 1
|
|
91
|
+
if n_dominated[j] == 0:
|
|
92
|
+
next_front.append(j)
|
|
93
|
+
ranked[j] = 1.0
|
|
94
|
+
n_ranked += 1
|
|
95
|
+
|
|
96
|
+
fronts.append(next_front)
|
|
97
|
+
current_front = next_front
|
|
98
|
+
|
|
99
|
+
return fronts
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _fast_biobjective_nondominated_sort(F):
|
|
103
|
+
"""
|
|
104
|
+
Specialized algorithm for bi-objective problems.
|
|
105
|
+
Uses the efficient skyline/multi-criteria approach with O(N log N) complexity.
|
|
106
|
+
"""
|
|
107
|
+
n_points = F.shape[0]
|
|
108
|
+
|
|
109
|
+
if n_points == 0:
|
|
110
|
+
return []
|
|
111
|
+
|
|
112
|
+
# Sort by first objective ascending
|
|
113
|
+
sorted_indices = np.argsort(F[:, 0])
|
|
114
|
+
sorted_F = F[sorted_indices]
|
|
115
|
+
|
|
116
|
+
fronts = []
|
|
117
|
+
assigned = [False] * n_points
|
|
118
|
+
n_assigned = 0
|
|
119
|
+
|
|
120
|
+
while n_assigned < n_points:
|
|
121
|
+
current_front = []
|
|
122
|
+
current_indices = []
|
|
123
|
+
|
|
124
|
+
# Track the minimum second objective seen in the current front
|
|
125
|
+
min_second_obj = float('inf')
|
|
126
|
+
|
|
127
|
+
for i in range(n_points):
|
|
128
|
+
if assigned[i]:
|
|
129
|
+
continue
|
|
130
|
+
|
|
131
|
+
# Check if current point is dominated by any point in current front
|
|
132
|
+
is_dominated = False
|
|
133
|
+
if current_indices: # If there are already points in the current front
|
|
134
|
+
# Since points are sorted by first objective, we only need to check
|
|
135
|
+
# if its second objective is greater than the minimum second objective in front
|
|
136
|
+
if sorted_F[i, 1] >= min_second_obj:
|
|
137
|
+
is_dominated = True
|
|
138
|
+
|
|
139
|
+
if not is_dominated:
|
|
140
|
+
# Add this point to the current front
|
|
141
|
+
current_front.append(sorted_indices[i])
|
|
142
|
+
current_indices.append(i)
|
|
143
|
+
assigned[i] = True
|
|
144
|
+
n_assigned += 1
|
|
145
|
+
# Update the minimum second objective
|
|
146
|
+
min_second_obj = min(min_second_obj, sorted_F[i, 1])
|
|
147
|
+
|
|
148
|
+
if current_front:
|
|
149
|
+
fronts.append(current_front)
|
|
150
|
+
else:
|
|
151
|
+
break
|
|
152
|
+
|
|
153
|
+
return fronts
|
|
154
|
+
|
|
155
|
+
def find_non_dominated(F, epsilon=0.0):
|
|
156
|
+
"""
|
|
157
|
+
Simple and efficient implementation to find only non-dominated points.
|
|
158
|
+
Uses straightforward O(n²) algorithm with early termination.
|
|
159
|
+
"""
|
|
160
|
+
n_points = F.shape[0]
|
|
161
|
+
non_dominated_indices = []
|
|
162
|
+
|
|
163
|
+
if n_points == 0:
|
|
164
|
+
return np.array([], dtype=int)
|
|
165
|
+
|
|
166
|
+
# Check each point to see if it's non-dominated
|
|
167
|
+
for i in range(n_points):
|
|
168
|
+
is_dominated = False
|
|
169
|
+
|
|
170
|
+
# Check if point i is dominated by any other point j
|
|
171
|
+
for j in range(n_points):
|
|
172
|
+
if i != j:
|
|
173
|
+
# Check if j dominates i
|
|
174
|
+
dominates = True
|
|
175
|
+
at_least_one_better = False
|
|
176
|
+
|
|
177
|
+
for k in range(F.shape[1]): # for each objective
|
|
178
|
+
if F[j, k] + epsilon < F[i, k]: # j is better than i in objective k
|
|
179
|
+
at_least_one_better = True
|
|
180
|
+
elif F[j, k] > F[i, k] + epsilon: # j is worse than i in objective k
|
|
181
|
+
dominates = False
|
|
182
|
+
break # Early termination in objective loop
|
|
183
|
+
|
|
184
|
+
# j dominates i if j is at least as good in all objectives and better in at least one
|
|
185
|
+
if dominates and at_least_one_better:
|
|
186
|
+
is_dominated = True
|
|
187
|
+
break # Early termination - no need to check other points
|
|
188
|
+
|
|
189
|
+
# If point i is not dominated by any other point, it's non-dominated
|
|
190
|
+
if not is_dominated:
|
|
191
|
+
non_dominated_indices.append(i)
|
|
192
|
+
|
|
193
|
+
return np.array(non_dominated_indices, dtype=int)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def efficient_non_dominated_sort(F, strategy="sequential"):
|
|
197
|
+
"""Efficient Non-dominated Sorting (ENS)"""
|
|
198
|
+
assert (strategy in ["sequential", 'binary']), "Invalid search strategy"
|
|
199
|
+
|
|
200
|
+
# the shape of the input
|
|
201
|
+
N, M = F.shape
|
|
202
|
+
|
|
203
|
+
# do a lexicographic ordering
|
|
204
|
+
I = np.lexsort(F.T[::-1])
|
|
205
|
+
F = F[I]
|
|
206
|
+
|
|
207
|
+
# front ranks for each individual
|
|
208
|
+
fronts = []
|
|
209
|
+
|
|
210
|
+
for i in range(N):
|
|
211
|
+
|
|
212
|
+
if strategy == 'sequential':
|
|
213
|
+
k = sequential_search(F, i, fronts)
|
|
214
|
+
else:
|
|
215
|
+
k = binary_search(F, i, fronts)
|
|
216
|
+
|
|
217
|
+
# create empty fronts if necessary
|
|
218
|
+
if k >= len(fronts):
|
|
219
|
+
fronts.append([])
|
|
220
|
+
|
|
221
|
+
# append the current individual to a front
|
|
222
|
+
fronts[k].append(i)
|
|
223
|
+
|
|
224
|
+
# now map the fronts back to the originally sorting
|
|
225
|
+
ret = []
|
|
226
|
+
for front in fronts:
|
|
227
|
+
ret.append(I[front])
|
|
228
|
+
|
|
229
|
+
return ret
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def sequential_search(F, i, fronts) -> int:
|
|
233
|
+
"""Find the front rank for the i-th individual through sequential search."""
|
|
234
|
+
num_found_fronts = len(fronts)
|
|
235
|
+
k = 0 # the front now checked
|
|
236
|
+
current = F[i]
|
|
237
|
+
while True:
|
|
238
|
+
if num_found_fronts == 0:
|
|
239
|
+
return 0
|
|
240
|
+
# solutions in the k-th front, examine in reverse order
|
|
241
|
+
fk_indices = fronts[k]
|
|
242
|
+
solutions = F[fk_indices[::-1]]
|
|
243
|
+
non_dominated = True
|
|
244
|
+
for f in solutions:
|
|
245
|
+
relation = Dominator.get_relation(current, f)
|
|
246
|
+
if relation == -1:
|
|
247
|
+
non_dominated = False
|
|
248
|
+
break
|
|
249
|
+
if non_dominated:
|
|
250
|
+
return k
|
|
251
|
+
else:
|
|
252
|
+
k += 1
|
|
253
|
+
if k >= num_found_fronts:
|
|
254
|
+
# move the individual to a new front
|
|
255
|
+
return num_found_fronts
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def binary_search(F, i, fronts):
|
|
259
|
+
"""Find the front rank for the i-th individual through binary search."""
|
|
260
|
+
num_found_fronts = len(fronts)
|
|
261
|
+
if num_found_fronts == 0:
|
|
262
|
+
return 0
|
|
263
|
+
|
|
264
|
+
k_min = 0 # the lower bound for checking
|
|
265
|
+
k_max = num_found_fronts # the upper bound for checking
|
|
266
|
+
k = floor((k_max + k_min) / 2 + 0.5) # the front now checked
|
|
267
|
+
current = F[i]
|
|
268
|
+
while True:
|
|
269
|
+
|
|
270
|
+
# solutions in the k-th front, examine in reverse order
|
|
271
|
+
fk_indices = fronts[k - 1]
|
|
272
|
+
solutions = F[fk_indices[::-1]]
|
|
273
|
+
non_dominated = True
|
|
274
|
+
|
|
275
|
+
for f in solutions:
|
|
276
|
+
relation = Dominator.get_relation(current, f)
|
|
277
|
+
if relation == -1:
|
|
278
|
+
non_dominated = False
|
|
279
|
+
break
|
|
280
|
+
|
|
281
|
+
# binary search
|
|
282
|
+
if non_dominated:
|
|
283
|
+
if k == k_min + 1:
|
|
284
|
+
return k - 1
|
|
285
|
+
else:
|
|
286
|
+
k_max = k
|
|
287
|
+
k = floor((k_max + k_min) / 2 + 0.5)
|
|
288
|
+
else:
|
|
289
|
+
k_min = k
|
|
290
|
+
if k_max == k_min + 1 and k_max < num_found_fronts:
|
|
291
|
+
return k_max - 1
|
|
292
|
+
elif k_min == num_found_fronts:
|
|
293
|
+
return num_found_fronts
|
|
294
|
+
else:
|
|
295
|
+
k = floor((k_max + k_min) / 2 + 0.5)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
class Tree:
|
|
299
|
+
"""Implementation of N-ary tree for tree-based non-dominated sorting."""
|
|
300
|
+
|
|
301
|
+
def __init__(self, key, num_branch, children=None, parent=None):
|
|
302
|
+
self.key = key
|
|
303
|
+
self.children = children or [None for _ in range(num_branch)]
|
|
304
|
+
self._parent = weakref.ref(parent) if parent else None
|
|
305
|
+
|
|
306
|
+
@property
|
|
307
|
+
def parent(self):
|
|
308
|
+
if self._parent:
|
|
309
|
+
return self._parent()
|
|
310
|
+
|
|
311
|
+
def __getstate__(self):
|
|
312
|
+
self._parent = None
|
|
313
|
+
|
|
314
|
+
def __setstate__(self, state):
|
|
315
|
+
self.__dict__ = state
|
|
316
|
+
for child in self.children:
|
|
317
|
+
child._parent = weakref.ref(self)
|
|
318
|
+
|
|
319
|
+
def traversal(self, visit=None, *args, **kwargs):
|
|
320
|
+
if visit is not None:
|
|
321
|
+
visit(self, *args, **kwargs)
|
|
322
|
+
l = [self]
|
|
323
|
+
for child in self.children:
|
|
324
|
+
if child is not None:
|
|
325
|
+
l += child.traversal(visit, *args, **kwargs)
|
|
326
|
+
return l
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def tree_based_non_dominated_sort(F):
|
|
330
|
+
"""Tree-based efficient non-dominated sorting (T-ENS)."""
|
|
331
|
+
N, M = F.shape
|
|
332
|
+
# sort the rows in F
|
|
333
|
+
indices = np.lexsort(F.T[::-1])
|
|
334
|
+
F = F[indices]
|
|
335
|
+
|
|
336
|
+
obj_seq = np.argsort(F[:, :0:-1], axis=1) + 1
|
|
337
|
+
|
|
338
|
+
k = 0
|
|
339
|
+
|
|
340
|
+
forest = []
|
|
341
|
+
|
|
342
|
+
left = np.full(N, True)
|
|
343
|
+
while np.any(left):
|
|
344
|
+
forest.append(None)
|
|
345
|
+
for p, flag in enumerate(left):
|
|
346
|
+
if flag:
|
|
347
|
+
update_tree(F, p, forest, k, left, obj_seq)
|
|
348
|
+
k += 1
|
|
349
|
+
|
|
350
|
+
# convert forest to fronts
|
|
351
|
+
fronts = [[] for _ in range(k)]
|
|
352
|
+
for k, tree in enumerate(forest):
|
|
353
|
+
fronts[k].extend([indices[node.key] for node in tree.traversal()])
|
|
354
|
+
return fronts
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def update_tree(F, p, forest, k, left, obj_seq):
|
|
358
|
+
"""Update tree for tree-based non-dominated sorting."""
|
|
359
|
+
_, M = F.shape
|
|
360
|
+
if forest[k] is None:
|
|
361
|
+
forest[k] = Tree(key=p, num_branch=M - 1)
|
|
362
|
+
left[p] = False
|
|
363
|
+
elif check_tree(F, p, forest[k], obj_seq, True):
|
|
364
|
+
left[p] = False
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def check_tree(F, p, tree, obj_seq, add_pos):
|
|
368
|
+
"""Check tree for tree-based non-dominated sorting."""
|
|
369
|
+
if tree is None:
|
|
370
|
+
return True
|
|
371
|
+
|
|
372
|
+
N, M = F.shape
|
|
373
|
+
|
|
374
|
+
# find the minimal index m satisfying that p[obj_seq[tree.root][m]] < tree.root[obj_seq[tree.root][m]]
|
|
375
|
+
m = 0
|
|
376
|
+
while m < M - 1 and F[p, obj_seq[tree.key, m]] >= F[tree.key, obj_seq[tree.key, m]]:
|
|
377
|
+
m += 1
|
|
378
|
+
|
|
379
|
+
# if m not found
|
|
380
|
+
if m == M - 1:
|
|
381
|
+
# p is dominated by the solution at the root
|
|
382
|
+
return False
|
|
383
|
+
else:
|
|
384
|
+
for i in range(m + 1):
|
|
385
|
+
# p is dominated by a solution in the branch of the tree
|
|
386
|
+
if not check_tree(F, p, tree.children[i], obj_seq, i == m and add_pos):
|
|
387
|
+
return False
|
|
388
|
+
|
|
389
|
+
if tree.children[m] is None and add_pos:
|
|
390
|
+
# add p to the branch of the tree
|
|
391
|
+
tree.children[m] = Tree(key=p, num_branch=M - 1)
|
|
392
|
+
return True
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def construct_comp_matrix(vec: np.ndarray, sorted_idx: np.ndarray) -> np.ndarray:
|
|
396
|
+
"""Construct the comparison matrix from a row-vector vec."""
|
|
397
|
+
n = vec.shape[0]
|
|
398
|
+
c = np.zeros(shape=(n, n), dtype=np.int32)
|
|
399
|
+
|
|
400
|
+
# the elements of the b(0)-th row in C are all set to 1
|
|
401
|
+
c[sorted_idx[0], :] = 1
|
|
402
|
+
|
|
403
|
+
for i in range(1, n):
|
|
404
|
+
if vec[sorted_idx[i]] == vec[sorted_idx[i - 1]]:
|
|
405
|
+
# the rows in C corresponding to the same elements in w are identical
|
|
406
|
+
c[sorted_idx[i]] = c[sorted_idx[i - 1]]
|
|
407
|
+
else:
|
|
408
|
+
c[sorted_idx[i], sorted_idx[i:]] = 1
|
|
409
|
+
|
|
410
|
+
return c
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def construct_domination_matrix(f_scores: np.ndarray, **kwargs) -> np.ndarray:
|
|
414
|
+
"""Calculate the dominance degree matrix for a set of vectors."""
|
|
415
|
+
d = np.zeros((f_scores.shape[0], f_scores.shape[0]), dtype=np.int32)
|
|
416
|
+
b = np.apply_over_axes(np.argsort, f_scores, axes=0)
|
|
417
|
+
for vec, srt in zip(f_scores.T, b.T):
|
|
418
|
+
d += construct_comp_matrix(vec, srt)
|
|
419
|
+
d = np.where(
|
|
420
|
+
np.logical_and(d == f_scores.shape[-1], d.T == f_scores.shape[-1]), 0, d
|
|
421
|
+
)
|
|
422
|
+
return d
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def dda_ns(f_scores: np.ndarray, **kwargs) -> List[List[int]]:
|
|
426
|
+
"""DDA-NS algorithm."""
|
|
427
|
+
d_mx = construct_domination_matrix(f_scores)
|
|
428
|
+
max_d = np.empty((f_scores.shape[0],), dtype=np.int32)
|
|
429
|
+
|
|
430
|
+
fronts = []
|
|
431
|
+
count = 0
|
|
432
|
+
while count < f_scores.shape[0]:
|
|
433
|
+
# Max(D) is the row vector containing the maximum elements from each column of D
|
|
434
|
+
np.max(d_mx, out=max_d, axis=0)
|
|
435
|
+
front = [i for i, m_d in enumerate(max_d) if 0 <= m_d < f_scores.shape[-1]]
|
|
436
|
+
count += len(front)
|
|
437
|
+
d_mx[front] = -1
|
|
438
|
+
d_mx[:, front] = -1
|
|
439
|
+
fronts.append(front)
|
|
440
|
+
|
|
441
|
+
return fronts
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def dda_ens(f_scores: np.ndarray, **kwargs) -> List[List[int]]:
|
|
445
|
+
"""DDA-ENS (efficient DDA) algorithm."""
|
|
446
|
+
d_mx = construct_domination_matrix(f_scores)
|
|
447
|
+
|
|
448
|
+
fronts: List[List[int]] = []
|
|
449
|
+
for s in np.lexsort(f_scores.T):
|
|
450
|
+
isinserted = False
|
|
451
|
+
for fk in fronts:
|
|
452
|
+
if not (d_mx[fk, s] == f_scores.shape[1]).any():
|
|
453
|
+
fk.append(s)
|
|
454
|
+
isinserted = True
|
|
455
|
+
break
|
|
456
|
+
if not isinserted:
|
|
457
|
+
fronts.append([s])
|
|
458
|
+
return fronts
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def dominance_degree_non_dominated_sort(
|
|
462
|
+
f_scores: np.ndarray, strategy: Literal["efficient", "fast"] = "efficient"
|
|
463
|
+
) -> List[List[int]]:
|
|
464
|
+
"""Perform non-dominating sort with the specified algorithm."""
|
|
465
|
+
if strategy == "efficient":
|
|
466
|
+
return dda_ens(f_scores)
|
|
467
|
+
if strategy == "fast":
|
|
468
|
+
return dda_ns(f_scores)
|
|
469
|
+
raise ValueError("Invalid search strategy")
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def fast_best_order_sort(*args, **kwargs):
|
|
473
|
+
"""Placeholder for fast_best_order_sort - only available in Cython."""
|
|
474
|
+
raise NotImplementedError("fast_best_order_sort is only available in compiled (Cython) version")
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Standard Python implementation of pruning with crowding distance.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def calc_pcd(X, n_remove=0):
|
|
9
|
+
"""Calculate pruning based on crowding distance."""
|
|
10
|
+
N = X.shape[0]
|
|
11
|
+
M = X.shape[1]
|
|
12
|
+
|
|
13
|
+
if n_remove <= (N - M):
|
|
14
|
+
if n_remove < 0:
|
|
15
|
+
n_remove = 0
|
|
16
|
+
else:
|
|
17
|
+
pass
|
|
18
|
+
else:
|
|
19
|
+
n_remove = N - M
|
|
20
|
+
|
|
21
|
+
extremes_min = np.argmin(X, axis=0)
|
|
22
|
+
extremes_max = np.argmax(X, axis=0)
|
|
23
|
+
|
|
24
|
+
min_vals = np.min(X, axis=0)
|
|
25
|
+
max_vals = np.max(X, axis=0)
|
|
26
|
+
|
|
27
|
+
extremes = np.concatenate((extremes_min, extremes_max))
|
|
28
|
+
|
|
29
|
+
X = (X - min_vals) / (max_vals - min_vals)
|
|
30
|
+
|
|
31
|
+
H = np.arange(N)
|
|
32
|
+
d = np.full(N, np.inf)
|
|
33
|
+
|
|
34
|
+
I = np.argsort(X, axis=0, kind='mergesort')
|
|
35
|
+
|
|
36
|
+
# sort the objective space values for the whole matrix
|
|
37
|
+
_X = X[I, np.arange(M)]
|
|
38
|
+
|
|
39
|
+
# calculate the distance from each point to the last and next
|
|
40
|
+
dist = np.vstack([_X, np.full(M, np.inf)]) - np.vstack([np.full(M, -np.inf), _X])
|
|
41
|
+
|
|
42
|
+
# prepare the distance to last and next vectors
|
|
43
|
+
dist_to_last, dist_to_next = dist, np.copy(dist)
|
|
44
|
+
dist_to_last, dist_to_next = dist_to_last[:-1], dist_to_next[1:]
|
|
45
|
+
|
|
46
|
+
# if we divide by zero because all values in one columns are equal replace by none
|
|
47
|
+
dist_to_last[np.isnan(dist_to_last)] = 0.0
|
|
48
|
+
dist_to_next[np.isnan(dist_to_next)] = 0.0
|
|
49
|
+
|
|
50
|
+
# sum up the distance to next and last and norm by objectives - also reorder from sorted list
|
|
51
|
+
J = np.argsort(I, axis=0)
|
|
52
|
+
_d = np.sum(dist_to_last[J, np.arange(M)] + dist_to_next[J, np.arange(M)], axis=1)
|
|
53
|
+
d[H] = _d
|
|
54
|
+
d[extremes] = np.inf
|
|
55
|
+
|
|
56
|
+
n_removed = 0
|
|
57
|
+
|
|
58
|
+
# While n_remove not achieved
|
|
59
|
+
while n_removed < (n_remove - 1):
|
|
60
|
+
|
|
61
|
+
# Obtain element to drop
|
|
62
|
+
_d = d[H]
|
|
63
|
+
_k = np.argmin(_d)
|
|
64
|
+
k = H[_k]
|
|
65
|
+
|
|
66
|
+
H = H[H != k]
|
|
67
|
+
|
|
68
|
+
# Update index
|
|
69
|
+
n_removed = n_removed + 1
|
|
70
|
+
|
|
71
|
+
I = np.argsort(X[H].copy(), axis=0, kind='mergesort')
|
|
72
|
+
|
|
73
|
+
# sort the objective space values for the whole matrix
|
|
74
|
+
_X = X[H].copy()[I, np.arange(M)]
|
|
75
|
+
|
|
76
|
+
# calculate the distance from each point to the last and next
|
|
77
|
+
dist = np.vstack([_X, np.full(M, np.inf)]) - np.vstack([np.full(M, -np.inf), _X])
|
|
78
|
+
|
|
79
|
+
# prepare the distance to last and next vectors
|
|
80
|
+
dist_to_last, dist_to_next = dist, np.copy(dist)
|
|
81
|
+
dist_to_last, dist_to_next = dist_to_last[:-1], dist_to_next[1:]
|
|
82
|
+
|
|
83
|
+
# if we divide by zero because all values in one columns are equal replace by none
|
|
84
|
+
dist_to_last[np.isnan(dist_to_last)] = 0.0
|
|
85
|
+
dist_to_next[np.isnan(dist_to_next)] = 0.0
|
|
86
|
+
|
|
87
|
+
# sum up the distance to next and last and norm by objectives - also reorder from sorted list
|
|
88
|
+
J = np.argsort(I, axis=0)
|
|
89
|
+
_d = np.sum(dist_to_last[J, np.arange(M)] + dist_to_next[J, np.arange(M)], axis=1)
|
|
90
|
+
d[H] = _d
|
|
91
|
+
d[extremes] = np.inf
|
|
92
|
+
|
|
93
|
+
return d
|