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
pymoo/core/algorithm.py
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from pymoo.core.callback import Callback
|
|
7
|
+
from pymoo.core.evaluator import Evaluator
|
|
8
|
+
from pymoo.core.meta import Meta
|
|
9
|
+
from pymoo.core.population import Population
|
|
10
|
+
from pymoo.core.result import Result
|
|
11
|
+
from pymoo.functions import FunctionLoader
|
|
12
|
+
from pymoo.termination.default import DefaultMultiObjectiveTermination, DefaultSingleObjectiveTermination
|
|
13
|
+
from pymoo.util.display.display import Display
|
|
14
|
+
from pymoo.util.misc import termination_from_tuple
|
|
15
|
+
from pymoo.util.optimum import filter_optimum
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Algorithm:
|
|
19
|
+
|
|
20
|
+
def __init__(self,
|
|
21
|
+
termination=None,
|
|
22
|
+
output=None,
|
|
23
|
+
display=None,
|
|
24
|
+
callback=None,
|
|
25
|
+
archive=None,
|
|
26
|
+
return_least_infeasible=False,
|
|
27
|
+
save_history=False,
|
|
28
|
+
verbose=False,
|
|
29
|
+
seed=None,
|
|
30
|
+
evaluator=None,
|
|
31
|
+
**kwargs):
|
|
32
|
+
|
|
33
|
+
super().__init__()
|
|
34
|
+
|
|
35
|
+
# prints the compile warning if enabled
|
|
36
|
+
FunctionLoader.get_instance()
|
|
37
|
+
|
|
38
|
+
# the problem to be solved (will be set later on)
|
|
39
|
+
self.problem = None
|
|
40
|
+
|
|
41
|
+
# the termination criterion to be used by the algorithm - might be specific for an algorithm
|
|
42
|
+
self.termination = termination
|
|
43
|
+
|
|
44
|
+
# the text that should be printed during the algorithm run
|
|
45
|
+
self.output = output
|
|
46
|
+
|
|
47
|
+
# an archive kept during algorithm execution (not always the same as optimum)
|
|
48
|
+
self.archive = archive
|
|
49
|
+
|
|
50
|
+
# the form of display shown during algorithm execution
|
|
51
|
+
self.display = display
|
|
52
|
+
|
|
53
|
+
# callback to be executed each generation
|
|
54
|
+
if callback is None:
|
|
55
|
+
callback = Callback()
|
|
56
|
+
self.callback = callback
|
|
57
|
+
|
|
58
|
+
# whether the algorithm should finally return the least infeasible solution if no feasible found
|
|
59
|
+
self.return_least_infeasible = return_least_infeasible
|
|
60
|
+
|
|
61
|
+
# whether the history should be saved or not
|
|
62
|
+
self.save_history = save_history
|
|
63
|
+
|
|
64
|
+
# whether the algorithm should print output in this run or not
|
|
65
|
+
self.verbose = verbose
|
|
66
|
+
|
|
67
|
+
# the random seed that was used
|
|
68
|
+
self.seed = seed
|
|
69
|
+
self.random_state = None
|
|
70
|
+
|
|
71
|
+
# the function evaluator object (can be used to inject code)
|
|
72
|
+
if evaluator is None:
|
|
73
|
+
evaluator = Evaluator()
|
|
74
|
+
self.evaluator = evaluator
|
|
75
|
+
|
|
76
|
+
# the history object which contains the list
|
|
77
|
+
self.history = list()
|
|
78
|
+
|
|
79
|
+
# the current solutions stored - here considered as population
|
|
80
|
+
self.pop = None
|
|
81
|
+
|
|
82
|
+
# a placeholder object for implementation to store solutions in each iteration
|
|
83
|
+
self.off = None
|
|
84
|
+
|
|
85
|
+
# the optimum found by the algorithm
|
|
86
|
+
self.opt = None
|
|
87
|
+
|
|
88
|
+
# the current number of generation or iteration
|
|
89
|
+
self.n_iter = None
|
|
90
|
+
|
|
91
|
+
# can be used to store additional data in submodules
|
|
92
|
+
self.data = {}
|
|
93
|
+
|
|
94
|
+
# if the initialized method has been called before or not
|
|
95
|
+
self.is_initialized = False
|
|
96
|
+
|
|
97
|
+
# the time when the algorithm has been setup for the first time
|
|
98
|
+
self.start_time = None
|
|
99
|
+
|
|
100
|
+
def setup(self, problem, verbose=False, progress=False, **kwargs):
|
|
101
|
+
|
|
102
|
+
# the problem to be solved by the algorithm
|
|
103
|
+
self.problem = problem
|
|
104
|
+
|
|
105
|
+
# clone the output object if it exists to avoid state pollution between runs
|
|
106
|
+
if self.output is not None:
|
|
107
|
+
self.output = copy.deepcopy(self.output)
|
|
108
|
+
|
|
109
|
+
# set all the provided options to this method
|
|
110
|
+
for key, value in kwargs.items():
|
|
111
|
+
self.__dict__[key] = value
|
|
112
|
+
|
|
113
|
+
# set random state
|
|
114
|
+
self.random_state = np.random.default_rng(self.seed)
|
|
115
|
+
|
|
116
|
+
# make sure that some type of termination criterion is set
|
|
117
|
+
if self.termination is None:
|
|
118
|
+
self.termination = default_termination(problem)
|
|
119
|
+
else:
|
|
120
|
+
self.termination = termination_from_tuple(self.termination)
|
|
121
|
+
|
|
122
|
+
# set up the display during the algorithm execution
|
|
123
|
+
if self.display is None:
|
|
124
|
+
self.display = Display(self.output, verbose=verbose, progress=progress)
|
|
125
|
+
|
|
126
|
+
# finally call the function that can be overwritten by the actual algorithm
|
|
127
|
+
self._setup(problem, **kwargs)
|
|
128
|
+
|
|
129
|
+
return self
|
|
130
|
+
|
|
131
|
+
def run(self):
|
|
132
|
+
while self.has_next():
|
|
133
|
+
self.next()
|
|
134
|
+
return self.result()
|
|
135
|
+
|
|
136
|
+
def has_next(self):
|
|
137
|
+
return not self.termination.has_terminated()
|
|
138
|
+
|
|
139
|
+
def finalize(self):
|
|
140
|
+
|
|
141
|
+
# finalize the display output in the end of the run
|
|
142
|
+
self.display.finalize()
|
|
143
|
+
|
|
144
|
+
return self._finalize()
|
|
145
|
+
|
|
146
|
+
def next(self):
|
|
147
|
+
|
|
148
|
+
# get the infill solutions
|
|
149
|
+
infills = self.infill()
|
|
150
|
+
|
|
151
|
+
# call the advance with them after evaluation
|
|
152
|
+
if infills is not None:
|
|
153
|
+
self.evaluator.eval(self.problem, infills, algorithm=self)
|
|
154
|
+
self.advance(infills=infills)
|
|
155
|
+
|
|
156
|
+
# if the algorithm does not follow the infill-advance scheme just call advance
|
|
157
|
+
else:
|
|
158
|
+
self.advance()
|
|
159
|
+
|
|
160
|
+
def _initialize(self):
|
|
161
|
+
|
|
162
|
+
# the time starts whenever this method is called
|
|
163
|
+
self.start_time = time.time()
|
|
164
|
+
|
|
165
|
+
# set the attribute for the optimization method to start
|
|
166
|
+
self.n_iter = 1
|
|
167
|
+
self.pop = Population.empty()
|
|
168
|
+
self.opt = None
|
|
169
|
+
|
|
170
|
+
def infill(self):
|
|
171
|
+
if self.problem is None:
|
|
172
|
+
raise Exception("Please call `setup(problem)` before calling next().")
|
|
173
|
+
|
|
174
|
+
# the first time next is called simply initial the algorithm - makes the interface cleaner
|
|
175
|
+
if not self.is_initialized:
|
|
176
|
+
|
|
177
|
+
# hook mostly used by the class to happen before even to initialize
|
|
178
|
+
self._initialize()
|
|
179
|
+
|
|
180
|
+
# execute the initialization infill of the algorithm
|
|
181
|
+
infills = self._initialize_infill()
|
|
182
|
+
|
|
183
|
+
else:
|
|
184
|
+
# request the infill solutions if the algorithm has implemented it
|
|
185
|
+
infills = self._infill()
|
|
186
|
+
|
|
187
|
+
# set the current generation to the offsprings
|
|
188
|
+
if infills is not None:
|
|
189
|
+
infills.set("n_gen", self.n_iter)
|
|
190
|
+
infills.set("n_iter", self.n_iter)
|
|
191
|
+
|
|
192
|
+
return infills
|
|
193
|
+
|
|
194
|
+
def advance(self, infills=None, **kwargs):
|
|
195
|
+
|
|
196
|
+
# if infills have been provided set them as offsprings and feed them into advance
|
|
197
|
+
self.off = infills
|
|
198
|
+
|
|
199
|
+
# if the algorithm has not been already initialized
|
|
200
|
+
if not self.is_initialized:
|
|
201
|
+
|
|
202
|
+
# set the generation counter to 1
|
|
203
|
+
self.n_iter = 1
|
|
204
|
+
|
|
205
|
+
# assign the population to the algorithm
|
|
206
|
+
self.pop = infills
|
|
207
|
+
|
|
208
|
+
# do what is necessary after the initialization
|
|
209
|
+
self._initialize_advance(infills=infills, **kwargs)
|
|
210
|
+
|
|
211
|
+
# set this algorithm to be initialized
|
|
212
|
+
self.is_initialized = True
|
|
213
|
+
|
|
214
|
+
# always advance to the next iteration after initialization
|
|
215
|
+
self._post_advance()
|
|
216
|
+
|
|
217
|
+
else:
|
|
218
|
+
|
|
219
|
+
# call the implementation of the advance method - if the infill is not None
|
|
220
|
+
val = self._advance(infills=infills, **kwargs)
|
|
221
|
+
|
|
222
|
+
# always advance to the next iteration - except if the algorithm returns False
|
|
223
|
+
if val is None or val:
|
|
224
|
+
self._post_advance()
|
|
225
|
+
|
|
226
|
+
# if the algorithm has terminated, then do the finalization steps and return the result
|
|
227
|
+
if self.termination.has_terminated():
|
|
228
|
+
self.finalize()
|
|
229
|
+
ret = self.result()
|
|
230
|
+
|
|
231
|
+
# otherwise just increase the iteration counter for the next step and return the current optimum
|
|
232
|
+
else:
|
|
233
|
+
ret = self.opt
|
|
234
|
+
|
|
235
|
+
# add the infill solutions to an archive
|
|
236
|
+
if self.archive is not None and infills is not None:
|
|
237
|
+
self.archive = self.archive.add(infills)
|
|
238
|
+
|
|
239
|
+
return ret
|
|
240
|
+
|
|
241
|
+
def result(self):
|
|
242
|
+
res = Result()
|
|
243
|
+
|
|
244
|
+
# store the time when the algorithm as finished
|
|
245
|
+
res.start_time = self.start_time
|
|
246
|
+
res.end_time = time.time()
|
|
247
|
+
res.exec_time = res.end_time - res.start_time
|
|
248
|
+
|
|
249
|
+
res.pop = self.pop
|
|
250
|
+
res.archive = self.archive
|
|
251
|
+
res.data = self.data
|
|
252
|
+
|
|
253
|
+
# get the optimal solution found
|
|
254
|
+
opt = self.opt
|
|
255
|
+
if opt is None or len(opt) == 0:
|
|
256
|
+
opt = None
|
|
257
|
+
|
|
258
|
+
# if no feasible solution has been found
|
|
259
|
+
elif not np.any(opt.get("FEAS")):
|
|
260
|
+
if self.return_least_infeasible:
|
|
261
|
+
opt = filter_optimum(opt, least_infeasible=True)
|
|
262
|
+
else:
|
|
263
|
+
opt = None
|
|
264
|
+
res.opt = opt
|
|
265
|
+
|
|
266
|
+
# if optimum is set to none to not report anything
|
|
267
|
+
if res.opt is None:
|
|
268
|
+
X, F, CV, G, H = None, None, None, None, None
|
|
269
|
+
|
|
270
|
+
# otherwise get the values from the population
|
|
271
|
+
else:
|
|
272
|
+
X, F, CV, G, H = self.opt.get("X", "F", "CV", "G", "H")
|
|
273
|
+
|
|
274
|
+
# if single-objective problem and only one solution was found - create a 1d array
|
|
275
|
+
if self.problem.n_obj == 1 and len(X) == 1:
|
|
276
|
+
X, F, CV, G, H = X[0], F[0], CV[0], G[0], H[0]
|
|
277
|
+
|
|
278
|
+
# set all the individual values
|
|
279
|
+
res.X, res.F, res.CV, res.G, res.H = X, F, CV, G, H
|
|
280
|
+
|
|
281
|
+
# create the result object
|
|
282
|
+
res.problem = self.problem
|
|
283
|
+
res.history = self.history
|
|
284
|
+
|
|
285
|
+
return res
|
|
286
|
+
|
|
287
|
+
def ask(self):
|
|
288
|
+
return self.infill()
|
|
289
|
+
|
|
290
|
+
def tell(self, *args, **kwargs):
|
|
291
|
+
return self.advance(*args, **kwargs)
|
|
292
|
+
|
|
293
|
+
def _set_optimum(self):
|
|
294
|
+
self.opt = filter_optimum(self.pop, least_infeasible=True)
|
|
295
|
+
|
|
296
|
+
def _post_advance(self):
|
|
297
|
+
|
|
298
|
+
# update the current optimum of the algorithm
|
|
299
|
+
self._set_optimum()
|
|
300
|
+
|
|
301
|
+
# update the current termination condition of the algorithm
|
|
302
|
+
self.termination.update(self)
|
|
303
|
+
|
|
304
|
+
# display the output if defined by the algorithm
|
|
305
|
+
self.display(self)
|
|
306
|
+
|
|
307
|
+
if self.save_history:
|
|
308
|
+
_hist, _callback, _display = self.history, self.callback, self.display
|
|
309
|
+
|
|
310
|
+
self.history, self.callback, self.display = None, None, None
|
|
311
|
+
obj = copy.deepcopy(self)
|
|
312
|
+
|
|
313
|
+
self.history, self.callback, self.display = _hist, _callback, _display
|
|
314
|
+
self.history.append(obj)
|
|
315
|
+
|
|
316
|
+
# if a callback function is provided it is called after each iteration
|
|
317
|
+
self.callback(self)
|
|
318
|
+
|
|
319
|
+
self.n_iter += 1
|
|
320
|
+
|
|
321
|
+
# =========================================================================================================
|
|
322
|
+
# TO BE OVERWRITTEN
|
|
323
|
+
# =========================================================================================================
|
|
324
|
+
|
|
325
|
+
def _setup(self, problem, **kwargs):
|
|
326
|
+
pass
|
|
327
|
+
|
|
328
|
+
def _initialize_infill(self):
|
|
329
|
+
pass
|
|
330
|
+
|
|
331
|
+
def _initialize_advance(self, infills=None, **kwargs):
|
|
332
|
+
pass
|
|
333
|
+
|
|
334
|
+
def _infill(self):
|
|
335
|
+
pass
|
|
336
|
+
|
|
337
|
+
def _advance(self, infills=None, **kwargs):
|
|
338
|
+
pass
|
|
339
|
+
|
|
340
|
+
def _finalize(self):
|
|
341
|
+
pass
|
|
342
|
+
|
|
343
|
+
# =========================================================================================================
|
|
344
|
+
# CONVENIENCE
|
|
345
|
+
# =========================================================================================================
|
|
346
|
+
|
|
347
|
+
@property
|
|
348
|
+
def n_gen(self):
|
|
349
|
+
return self.n_iter
|
|
350
|
+
|
|
351
|
+
@n_gen.setter
|
|
352
|
+
def n_gen(self, value):
|
|
353
|
+
self.n_iter = value
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
class LoopwiseAlgorithm(Algorithm):
|
|
357
|
+
|
|
358
|
+
def __init__(self, **kwargs):
|
|
359
|
+
super().__init__(**kwargs)
|
|
360
|
+
self.generator = None
|
|
361
|
+
self.state = None
|
|
362
|
+
|
|
363
|
+
def _next(self):
|
|
364
|
+
pass
|
|
365
|
+
|
|
366
|
+
def _infill(self):
|
|
367
|
+
if self.state is None:
|
|
368
|
+
self._advance()
|
|
369
|
+
return self.state
|
|
370
|
+
|
|
371
|
+
def _advance(self, infills=None, **kwargs):
|
|
372
|
+
if self.generator is None:
|
|
373
|
+
self.generator = self._next()
|
|
374
|
+
try:
|
|
375
|
+
self.state = self.generator.send(infills)
|
|
376
|
+
except StopIteration:
|
|
377
|
+
self.generator = None
|
|
378
|
+
self.state = None
|
|
379
|
+
return True
|
|
380
|
+
|
|
381
|
+
return False
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def default_termination(problem):
|
|
385
|
+
if problem.n_obj > 1:
|
|
386
|
+
termination = DefaultMultiObjectiveTermination()
|
|
387
|
+
else:
|
|
388
|
+
termination = DefaultSingleObjectiveTermination()
|
|
389
|
+
return termination
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
class MetaAlgorithm(Meta):
|
|
393
|
+
"""
|
|
394
|
+
An algorithm wrapper that combines Algorithm's functionality with Meta's delegation behavior.
|
|
395
|
+
Uses Meta to provide transparent proxying with the ability to override specific methods.
|
|
396
|
+
"""
|
|
397
|
+
|
|
398
|
+
def __init__(self, algorithm, copy=True, **kwargs):
|
|
399
|
+
# If the algorithm is already a Meta object, don't copy to avoid deepcopy issues with nested proxies
|
|
400
|
+
if isinstance(algorithm, Meta):
|
|
401
|
+
copy = False
|
|
402
|
+
|
|
403
|
+
# Initialize Meta
|
|
404
|
+
super().__init__(algorithm, copy=copy)
|
|
405
|
+
|
|
406
|
+
# Pass any additional kwargs to the wrapped algorithm if needed
|
|
407
|
+
for key, value in kwargs.items():
|
|
408
|
+
setattr(self, key, value)
|
pymoo/core/callback.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
class Callback:
|
|
2
|
+
|
|
3
|
+
def __init__(self) -> None:
|
|
4
|
+
super().__init__()
|
|
5
|
+
self.data = {}
|
|
6
|
+
self.is_initialized = False
|
|
7
|
+
|
|
8
|
+
def initialize(self, algorithm):
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
def notify(self, algorithm):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
def update(self, algorithm):
|
|
15
|
+
return self._update(algorithm)
|
|
16
|
+
|
|
17
|
+
def _update(self, algorithm):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
def __call__(self, algorithm):
|
|
21
|
+
|
|
22
|
+
if not self.is_initialized:
|
|
23
|
+
self.initialize(algorithm)
|
|
24
|
+
self.is_initialized = True
|
|
25
|
+
|
|
26
|
+
self.notify(algorithm)
|
|
27
|
+
self.update(algorithm)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class CallbackCollection(Callback):
|
|
31
|
+
|
|
32
|
+
def __init__(self, *args) -> None:
|
|
33
|
+
super().__init__()
|
|
34
|
+
self.callbacks = args
|
|
35
|
+
|
|
36
|
+
def update(self, algorithm):
|
|
37
|
+
[callback.update(algorithm) for callback in self.callbacks]
|
|
38
|
+
|
pymoo/core/crossover.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from pymoo.core.operator import Operator
|
|
4
|
+
from pymoo.core.population import Population
|
|
5
|
+
from pymoo.core.variable import Real, get
|
|
6
|
+
from pymoo.util import default_random_state
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Crossover(Operator):
|
|
10
|
+
|
|
11
|
+
def __init__(self,
|
|
12
|
+
n_parents,
|
|
13
|
+
n_offsprings,
|
|
14
|
+
prob=0.9,
|
|
15
|
+
**kwargs):
|
|
16
|
+
super().__init__(**kwargs)
|
|
17
|
+
self.n_parents = n_parents
|
|
18
|
+
self.n_offsprings = n_offsprings
|
|
19
|
+
self.prob = Real(prob, bounds=(0.5, 1.0), strict=(0.0, 1.0))
|
|
20
|
+
|
|
21
|
+
@default_random_state
|
|
22
|
+
def do(self, problem, pop, parents=None, *args, random_state=None, **kwargs):
|
|
23
|
+
|
|
24
|
+
# if a parents with array with mating indices is provided -> transform the input first
|
|
25
|
+
if parents is not None:
|
|
26
|
+
pop = [pop[mating] for mating in parents]
|
|
27
|
+
|
|
28
|
+
# get the dimensions necessary to create in and output
|
|
29
|
+
n_parents, n_offsprings = self.n_parents, self.n_offsprings
|
|
30
|
+
n_matings, n_var = len(pop), problem.n_var
|
|
31
|
+
|
|
32
|
+
# get the actual values from each of the parents
|
|
33
|
+
X = np.swapaxes(np.array([[parent.get("X") for parent in mating] for mating in pop]), 0, 1)
|
|
34
|
+
if self.vtype is not None:
|
|
35
|
+
X = X.astype(self.vtype)
|
|
36
|
+
|
|
37
|
+
# the array where the offsprings will be stored to
|
|
38
|
+
Xp = np.empty(shape=(n_offsprings, n_matings, n_var), dtype=X.dtype)
|
|
39
|
+
|
|
40
|
+
# the probability of executing the crossover
|
|
41
|
+
prob = get(self.prob, size=n_matings)
|
|
42
|
+
|
|
43
|
+
# a boolean mask when crossover is actually executed
|
|
44
|
+
cross = random_state.random(n_matings) < prob
|
|
45
|
+
|
|
46
|
+
# the design space from the parents used for the crossover
|
|
47
|
+
if np.any(cross):
|
|
48
|
+
|
|
49
|
+
# we can not prefilter for cross first, because there might be other variables using the same shape as X
|
|
50
|
+
Q = self._do(problem, X, *args, random_state=random_state, **kwargs)
|
|
51
|
+
assert Q.shape == (n_offsprings, n_matings, problem.n_var), "Shape is incorrect of crossover impl."
|
|
52
|
+
Xp[:, cross] = Q[:, cross]
|
|
53
|
+
|
|
54
|
+
# now set the parents whenever NO crossover has been applied
|
|
55
|
+
for k in np.flatnonzero(~cross):
|
|
56
|
+
if n_offsprings < n_parents:
|
|
57
|
+
s = random_state.choice(np.arange(self.n_parents), size=n_offsprings, replace=False)
|
|
58
|
+
elif n_offsprings == n_parents:
|
|
59
|
+
s = np.arange(n_parents)
|
|
60
|
+
else:
|
|
61
|
+
s = []
|
|
62
|
+
while len(s) < n_offsprings:
|
|
63
|
+
s.extend(random_state.permutation(n_parents))
|
|
64
|
+
s = s[:n_offsprings]
|
|
65
|
+
|
|
66
|
+
Xp[:, k] = np.copy(X[s, k])
|
|
67
|
+
|
|
68
|
+
# flatten the array to become a 2d-array
|
|
69
|
+
Xp = Xp.reshape(-1, X.shape[-1])
|
|
70
|
+
|
|
71
|
+
# create a population object
|
|
72
|
+
off = Population.new("X", Xp)
|
|
73
|
+
|
|
74
|
+
return off
|
|
75
|
+
|
|
76
|
+
def _do(self, problem, X, *args, random_state=None, **kwargs):
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.spatial.ckdtree import cKDTree
|
|
3
|
+
|
|
4
|
+
from pymoo.core.indicator import Indicator
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DecisionMaking(Indicator):
|
|
8
|
+
|
|
9
|
+
def __init__(self, **kwargs) -> None:
|
|
10
|
+
super().__init__(**kwargs)
|
|
11
|
+
self.default_if_empty = None
|
|
12
|
+
|
|
13
|
+
def _do(self, F, *args, **kwargs):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class NeighborFinder:
|
|
18
|
+
|
|
19
|
+
def __init__(self, N,
|
|
20
|
+
epsilon=0.125,
|
|
21
|
+
n_neighbors=None,
|
|
22
|
+
n_min_neigbors=None,
|
|
23
|
+
consider_2d=True):
|
|
24
|
+
|
|
25
|
+
super().__init__()
|
|
26
|
+
self.N = N
|
|
27
|
+
self.consider_2d = consider_2d
|
|
28
|
+
|
|
29
|
+
_, n_dim = N.shape
|
|
30
|
+
|
|
31
|
+
# at least find min(dimensionality times two neighbors, number PO solutions - 1) - if enabled
|
|
32
|
+
if n_min_neigbors == "auto":
|
|
33
|
+
self.n_min_neigbors = min(2 * n_dim, _ - 1)
|
|
34
|
+
|
|
35
|
+
# disable the minimum neighbor variable
|
|
36
|
+
else:
|
|
37
|
+
self.n_min_neigbors = np.inf
|
|
38
|
+
|
|
39
|
+
# either choose epsilon
|
|
40
|
+
self.epsilon = epsilon
|
|
41
|
+
|
|
42
|
+
# if none choose the number of neighbors
|
|
43
|
+
self.n_neighbors = n_neighbors
|
|
44
|
+
|
|
45
|
+
if self.N.shape[1] == 1:
|
|
46
|
+
raise Exception("At least 2 objectives must be provided.")
|
|
47
|
+
|
|
48
|
+
elif self.consider_2d and self.N.shape[1] == 2:
|
|
49
|
+
self.min, self.max = N.min(), N.max()
|
|
50
|
+
self.rank = np.argsort(N[:, 0])
|
|
51
|
+
self.pos_in_rank = np.argsort(self.rank)
|
|
52
|
+
|
|
53
|
+
else:
|
|
54
|
+
self.tree = cKDTree(N)
|
|
55
|
+
|
|
56
|
+
def find(self, i):
|
|
57
|
+
|
|
58
|
+
if self.consider_2d and self.N.shape[1] == 2:
|
|
59
|
+
neighbours = []
|
|
60
|
+
|
|
61
|
+
pos = self.pos_in_rank[i]
|
|
62
|
+
if pos > 0:
|
|
63
|
+
neighbours.append(self.rank[pos - 1])
|
|
64
|
+
if pos < len(self.N) - 1:
|
|
65
|
+
neighbours.append(self.rank[pos + 1])
|
|
66
|
+
|
|
67
|
+
else:
|
|
68
|
+
|
|
69
|
+
# for each neighbour in a specific radius of that solution
|
|
70
|
+
if self.epsilon is not None:
|
|
71
|
+
neighbours = self.tree.query_ball_point([self.N[i]], self.epsilon).tolist()[0]
|
|
72
|
+
elif self.n_neighbors is not None:
|
|
73
|
+
neighbours = self.tree.query([self.N[i]], k=self.n_neighbors + 1)[1].tolist()[0]
|
|
74
|
+
else:
|
|
75
|
+
raise Exception("Either define epsilon or number of neighbors.")
|
|
76
|
+
|
|
77
|
+
# in case n_min_neigbors is enabled
|
|
78
|
+
if len(neighbours) < self.n_min_neigbors:
|
|
79
|
+
neighbours = self.tree.query([self.N[i]], k=self.n_min_neigbors + 1)[1].tolist()[0]
|
|
80
|
+
|
|
81
|
+
return neighbours
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def find_outliers_upper_tail(mu):
|
|
85
|
+
|
|
86
|
+
# remove values that are nan
|
|
87
|
+
I = np.where(np.logical_and(np.logical_not(np.isnan(mu)), np.logical_not(np.isinf(mu))))[0]
|
|
88
|
+
mu = mu[I]
|
|
89
|
+
|
|
90
|
+
# calculate mean and sigma
|
|
91
|
+
mean, sigma = mu.mean(), mu.std()
|
|
92
|
+
|
|
93
|
+
# calculate the deviation in terms of sigma
|
|
94
|
+
deviation = (mu - mean) / sigma
|
|
95
|
+
|
|
96
|
+
# 2 * sigma is considered as an outlier
|
|
97
|
+
S = I[np.where(deviation >= 2)[0]]
|
|
98
|
+
|
|
99
|
+
if len(S) == 0 and deviation.max() > 1:
|
|
100
|
+
S = I[[np.argmax(mu)]]
|
|
101
|
+
|
|
102
|
+
return S if len(S) > 0 else None
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from pymoo.util.misc import at_least_2d_array, to_1d_array_if_possible
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Decomposition:
|
|
7
|
+
|
|
8
|
+
def __init__(self, eps=0.0, _type="auto", **kwargs) -> None:
|
|
9
|
+
super().__init__()
|
|
10
|
+
self.eps = eps
|
|
11
|
+
self._type = _type
|
|
12
|
+
self.ideal_point, self.utopian_point, self.nadir_point = None, None, None
|
|
13
|
+
|
|
14
|
+
def __call__(self, *args, **kwargs):
|
|
15
|
+
return self.do(*args, **kwargs)
|
|
16
|
+
|
|
17
|
+
def do(self,
|
|
18
|
+
F,
|
|
19
|
+
weights,
|
|
20
|
+
_type="auto",
|
|
21
|
+
ideal_point=None,
|
|
22
|
+
utopian_point=None,
|
|
23
|
+
nadir_point=None,
|
|
24
|
+
**kwargs):
|
|
25
|
+
|
|
26
|
+
_F, _weights = to_1d_array_if_possible(F), to_1d_array_if_possible(weights)
|
|
27
|
+
|
|
28
|
+
if _type == "auto":
|
|
29
|
+
if _F.ndim == 1 and _weights.ndim > 1:
|
|
30
|
+
_type = "one_to_many"
|
|
31
|
+
elif _F.ndim > 1 and _weights.ndim == 1:
|
|
32
|
+
_type = "many_to_one"
|
|
33
|
+
elif _F.ndim == 2 and _weights.ndim == 2 and _F.shape[0] == _weights.shape[0]:
|
|
34
|
+
_type = "one_to_one"
|
|
35
|
+
else:
|
|
36
|
+
_type = "many_to_many"
|
|
37
|
+
|
|
38
|
+
# make both at least 2d arrays
|
|
39
|
+
F, weights = at_least_2d_array(F), at_least_2d_array(weights)
|
|
40
|
+
|
|
41
|
+
# get the number of points and weights
|
|
42
|
+
n_points, n_weights = F.shape[0], weights.shape[0]
|
|
43
|
+
|
|
44
|
+
self.ideal_point = ideal_point
|
|
45
|
+
if self.ideal_point is None:
|
|
46
|
+
self.ideal_point = np.zeros(F.shape[1])
|
|
47
|
+
|
|
48
|
+
self.utopian_point = utopian_point
|
|
49
|
+
if self.utopian_point is None:
|
|
50
|
+
self.utopian_point = self.ideal_point - self.eps
|
|
51
|
+
|
|
52
|
+
# set the nadir point by default to value or default
|
|
53
|
+
self.nadir_point = nadir_point
|
|
54
|
+
if self.nadir_point is None:
|
|
55
|
+
self.nadir_point = self.utopian_point + np.ones(F.shape[1])
|
|
56
|
+
|
|
57
|
+
if _type == "one_to_one":
|
|
58
|
+
D = self._do(F, weights=weights, **kwargs).flatten()
|
|
59
|
+
|
|
60
|
+
elif _type == "one_to_many":
|
|
61
|
+
F = np.repeat(F, n_weights, axis=0)
|
|
62
|
+
D = self._do(F, weights=weights, **kwargs).flatten()
|
|
63
|
+
|
|
64
|
+
elif _type == "many_to_one":
|
|
65
|
+
weights = np.repeat(weights, n_points, axis=0)
|
|
66
|
+
D = self._do(F, weights=weights, **kwargs).flatten()
|
|
67
|
+
|
|
68
|
+
elif _type == "many_to_many":
|
|
69
|
+
F = np.repeat(F, n_weights, axis=0)
|
|
70
|
+
weights = np.tile(weights, (n_points, 1))
|
|
71
|
+
D = self._do(F, weights=weights, **kwargs).reshape(n_points, n_weights)
|
|
72
|
+
|
|
73
|
+
else:
|
|
74
|
+
raise Exception("Unknown type for decomposition: %s" % _type)
|
|
75
|
+
|
|
76
|
+
return D
|