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
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from pymoo.gradient.grad_autograd import triu_indices, sqrt, log
|
|
4
|
+
from pymoo.util.ref_dirs.construction import ConstructionBasedReferenceDirectionFactory
|
|
5
|
+
from pymoo.util.ref_dirs.misc import project_onto_sum_equals_zero_plane, project_onto_unit_simplex_recursive
|
|
6
|
+
from pymoo.util.ref_dirs.optimizer import Adam
|
|
7
|
+
from pymoo.util.ref_dirs.reduction import ReductionBasedReferenceDirectionFactory
|
|
8
|
+
from pymoo.util.reference_direction import ReferenceDirectionFactory, scale_reference_directions
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RieszEnergyReferenceDirectionFactory(ReferenceDirectionFactory):
|
|
12
|
+
|
|
13
|
+
def __init__(self,
|
|
14
|
+
n_dim,
|
|
15
|
+
n_points,
|
|
16
|
+
ref_points=None,
|
|
17
|
+
return_as_tuple=False,
|
|
18
|
+
n_max_iter=1000,
|
|
19
|
+
n_until_optimizer_reset=30,
|
|
20
|
+
sampling="reduction",
|
|
21
|
+
norm_gradients=True,
|
|
22
|
+
verify_gradient=False,
|
|
23
|
+
freeze_edges=False,
|
|
24
|
+
precision=1e-5,
|
|
25
|
+
restarts=True,
|
|
26
|
+
X=None,
|
|
27
|
+
d=None,
|
|
28
|
+
callback=None,
|
|
29
|
+
**kwargs):
|
|
30
|
+
|
|
31
|
+
super().__init__(n_dim, **kwargs)
|
|
32
|
+
|
|
33
|
+
self.n_points = n_points
|
|
34
|
+
self.n_max_iter = n_max_iter
|
|
35
|
+
self.n_max_not_improved = n_until_optimizer_reset
|
|
36
|
+
self.return_as_tuple = return_as_tuple
|
|
37
|
+
self.sampling = sampling
|
|
38
|
+
self.X = X
|
|
39
|
+
self.ref_points = ref_points
|
|
40
|
+
self.precision = precision
|
|
41
|
+
self.verify_gradient = verify_gradient
|
|
42
|
+
self.norm_gradients = norm_gradients
|
|
43
|
+
self.freeze_edges = freeze_edges
|
|
44
|
+
self.d = d
|
|
45
|
+
self.callback = callback
|
|
46
|
+
self.restarts = restarts
|
|
47
|
+
|
|
48
|
+
# experiments have shown that dimensions squared is good value to choose here
|
|
49
|
+
if self.d is None:
|
|
50
|
+
self.d = n_dim * 2
|
|
51
|
+
|
|
52
|
+
def _step(self, optimizer, X, freeze=None):
|
|
53
|
+
free = np.logical_not(freeze)
|
|
54
|
+
|
|
55
|
+
obj, grad, mutual_dist = calc_potential_energy_with_grad(X, self.d, return_mutual_dist=True)
|
|
56
|
+
# obj, grad = value_and_grad(calc_potential_energy)(X, self.d)
|
|
57
|
+
|
|
58
|
+
if self.verify_gradient:
|
|
59
|
+
from autograd import value_and_grad
|
|
60
|
+
obj, grad = calc_potential_energy_with_grad(X, self.d)
|
|
61
|
+
_obj, _grad = value_and_grad(calc_potential_energy)(X, self.d)
|
|
62
|
+
if np.abs(grad - _grad).mean() > 1e-5:
|
|
63
|
+
print("GRADIENT IMPLEMENTATION IS INCORRECT!")
|
|
64
|
+
|
|
65
|
+
# set the gradients for frozen points to zero - make them not to move
|
|
66
|
+
if freeze is not None:
|
|
67
|
+
grad[freeze] = 0
|
|
68
|
+
|
|
69
|
+
# project the gradient to have a sum of zero - guarantees to stay on the simplex
|
|
70
|
+
proj_grad = project_onto_sum_equals_zero_plane(grad)
|
|
71
|
+
|
|
72
|
+
# normalize the gradients by the largest gradient norm
|
|
73
|
+
if self.norm_gradients:
|
|
74
|
+
norm = np.linalg.norm(proj_grad, axis=1)
|
|
75
|
+
proj_grad = (proj_grad / max(norm.max(), 1e-24))
|
|
76
|
+
|
|
77
|
+
# apply a step of gradient descent by subtracting the projected gradient with a learning rate
|
|
78
|
+
X = optimizer.next(X, proj_grad)
|
|
79
|
+
|
|
80
|
+
# project the out of bounds points back onto the unit simplex
|
|
81
|
+
X[free] = project_onto_unit_simplex_recursive(X[free])
|
|
82
|
+
|
|
83
|
+
# because of floating point issues make sure it is on the unit simplex
|
|
84
|
+
X /= X.sum(axis=1)[:, None]
|
|
85
|
+
|
|
86
|
+
return X, obj
|
|
87
|
+
|
|
88
|
+
def _solve(self, X, F=None, freeze_edges=True):
|
|
89
|
+
n_points = len(X)
|
|
90
|
+
ret, obj = X, np.inf
|
|
91
|
+
n_not_improved = 0
|
|
92
|
+
|
|
93
|
+
# get the edge mask
|
|
94
|
+
if freeze_edges:
|
|
95
|
+
freeze = np.any(X < 1e-16, axis=1)
|
|
96
|
+
else:
|
|
97
|
+
freeze = np.full(len(X), False)
|
|
98
|
+
|
|
99
|
+
# if additional points to be frozen are provided - add them to the X and mark them as frozen
|
|
100
|
+
if F is not None:
|
|
101
|
+
X = np.vstack([X, F])
|
|
102
|
+
freeze = np.concatenate([freeze, np.full(len(F), True)])
|
|
103
|
+
|
|
104
|
+
# if all points are frozen - simply return it
|
|
105
|
+
if np.all(freeze):
|
|
106
|
+
return X
|
|
107
|
+
|
|
108
|
+
# initialize the optimizer for the run
|
|
109
|
+
self.optimizer = Adam(alpha=0.005)
|
|
110
|
+
|
|
111
|
+
if self.callback is not None:
|
|
112
|
+
self.callback(self, X)
|
|
113
|
+
|
|
114
|
+
# for each iteration of gradient descent
|
|
115
|
+
for i in range(self.n_max_iter):
|
|
116
|
+
|
|
117
|
+
# execute one optimization step
|
|
118
|
+
_X, _obj = self._step(self.optimizer, X, freeze=freeze)
|
|
119
|
+
|
|
120
|
+
# if it is the current best solution -> store it
|
|
121
|
+
if _obj < obj:
|
|
122
|
+
ret, obj, n_not_improved = _X, _obj, 0
|
|
123
|
+
else:
|
|
124
|
+
n_not_improved += 1
|
|
125
|
+
|
|
126
|
+
# evaluate how much the points have moved
|
|
127
|
+
delta = np.sqrt((_X[:n_points] - X[:n_points]) ** 2).mean(axis=1).mean()
|
|
128
|
+
|
|
129
|
+
if self.verbose:
|
|
130
|
+
print(i, "objective", _obj, "delta", delta)
|
|
131
|
+
|
|
132
|
+
# if there was only a little delta during the last iteration -> terminate
|
|
133
|
+
if delta < self.precision or np.isnan(_obj):
|
|
134
|
+
break
|
|
135
|
+
|
|
136
|
+
# reset the optimizer if the objective value has not improved x iterations
|
|
137
|
+
if self.restarts and n_not_improved > self.n_max_not_improved:
|
|
138
|
+
self.optimizer = Adam(alpha=self.optimizer.alpha / 2)
|
|
139
|
+
_X = ret
|
|
140
|
+
n_not_improved = 0
|
|
141
|
+
|
|
142
|
+
# otherwise use the new points for the next iteration
|
|
143
|
+
X = _X
|
|
144
|
+
|
|
145
|
+
if self.callback is not None:
|
|
146
|
+
self.callback(self, X)
|
|
147
|
+
|
|
148
|
+
return ret[:n_points]
|
|
149
|
+
|
|
150
|
+
def _do(self, random_state=None):
|
|
151
|
+
X = self.X
|
|
152
|
+
|
|
153
|
+
# if no initial points are provided by the user
|
|
154
|
+
if X is None:
|
|
155
|
+
if self.sampling == "reduction":
|
|
156
|
+
X = ReductionBasedReferenceDirectionFactory(self.n_dim,
|
|
157
|
+
self.n_points,
|
|
158
|
+
kmeans=True,
|
|
159
|
+
lexsort=False) \
|
|
160
|
+
.do(random_state=random_state)
|
|
161
|
+
|
|
162
|
+
elif self.sampling == "construction":
|
|
163
|
+
X = ConstructionBasedReferenceDirectionFactory(self.n_dim,
|
|
164
|
+
self.n_points) \
|
|
165
|
+
.do(random_state=random_state)
|
|
166
|
+
else:
|
|
167
|
+
raise Exception("Unknown sampling method. Either reduction or construction.")
|
|
168
|
+
|
|
169
|
+
X = self._solve(X, freeze_edges=self.freeze_edges)
|
|
170
|
+
|
|
171
|
+
if self.ref_points is not None:
|
|
172
|
+
X, R = self.calc_ref_points(X, self.ref_points)
|
|
173
|
+
|
|
174
|
+
if self.return_as_tuple:
|
|
175
|
+
return X, R
|
|
176
|
+
else:
|
|
177
|
+
return np.vstack([X, R])
|
|
178
|
+
|
|
179
|
+
return X
|
|
180
|
+
|
|
181
|
+
def calc_ref_points(self, X, ref_points):
|
|
182
|
+
n_points = len(X)
|
|
183
|
+
|
|
184
|
+
# the center needed for translations
|
|
185
|
+
centroid = np.full((1, self.n_dim), 1 / self.n_dim)
|
|
186
|
+
|
|
187
|
+
R = []
|
|
188
|
+
|
|
189
|
+
# for each reference point provided by the user
|
|
190
|
+
for entry in ref_points:
|
|
191
|
+
ref_point, n_points_of_ref = entry.get("coordinates"), entry.get("n_points")
|
|
192
|
+
scale, volume = entry.get("scale"), entry.get("volume")
|
|
193
|
+
|
|
194
|
+
if scale is None:
|
|
195
|
+
if volume is None:
|
|
196
|
+
raise Exception("Either define scale or volume!")
|
|
197
|
+
else:
|
|
198
|
+
scale = volume ** (self.n_dim - 1)
|
|
199
|
+
|
|
200
|
+
# retrieve all points to consider
|
|
201
|
+
_X = np.vstack([X] + R)
|
|
202
|
+
|
|
203
|
+
# translate X to make the simplex to fill the unit
|
|
204
|
+
v = centroid - ref_point
|
|
205
|
+
X_t = _X + v
|
|
206
|
+
X_t = scale_reference_directions(X_t, 1 / scale)
|
|
207
|
+
|
|
208
|
+
# find the indices of points which are used as edges
|
|
209
|
+
I = np.where(np.any(X_t < 1e-5, axis=1))[0]
|
|
210
|
+
|
|
211
|
+
# create new points in the simplex where reference directions are supposed to be optimized
|
|
212
|
+
_n_points = n_points_of_ref + (n_points - len(I))
|
|
213
|
+
_R = ReductionBasedReferenceDirectionFactory(self.n_dim, n_points=_n_points, kmeans=True,
|
|
214
|
+
lexsort=False).do()
|
|
215
|
+
|
|
216
|
+
# detect the edges and just optimize them and shrink later
|
|
217
|
+
outer = np.any(_R == 0, axis=1)
|
|
218
|
+
inner = ~outer
|
|
219
|
+
|
|
220
|
+
# rescale the reference directions to be not too close to existing points
|
|
221
|
+
_R = scale_reference_directions(_R, 0.9)
|
|
222
|
+
|
|
223
|
+
# optimize just the edges
|
|
224
|
+
_R[outer] = self._solve(_R[outer], F=np.vstack([X_t[I], _R[inner]]), freeze_edges=False)
|
|
225
|
+
|
|
226
|
+
# no set the reference point and freeze it
|
|
227
|
+
# closest_to_centroid = cdist(centroid, _R).argmin()
|
|
228
|
+
# outer[closest_to_centroid] = True
|
|
229
|
+
# inner[closest_to_centroid] = False
|
|
230
|
+
# _R[closest_to_centroid] = centroid
|
|
231
|
+
|
|
232
|
+
# now when translated the reference point becomes the centroid - fix that point to be included
|
|
233
|
+
_R[inner] = self._solve(_R[inner], F=np.vstack([X_t[I], _R[outer]]), freeze_edges=False)
|
|
234
|
+
|
|
235
|
+
# rescale and translate them back
|
|
236
|
+
_R_t = scale_reference_directions(_R, scale)
|
|
237
|
+
_R_t = _R_t - v
|
|
238
|
+
|
|
239
|
+
# if any point is out of bounds - volume was too large
|
|
240
|
+
if not np.all(_R_t >= 0):
|
|
241
|
+
|
|
242
|
+
# get the corner points if not transformed
|
|
243
|
+
V = (np.eye(self.n_dim) - centroid)
|
|
244
|
+
|
|
245
|
+
# get the corner points of the ref dir simplex
|
|
246
|
+
P = ref_point + scale * V
|
|
247
|
+
E = centroid + scale * V
|
|
248
|
+
|
|
249
|
+
# project because at least is out of points
|
|
250
|
+
P_proj = project_onto_unit_simplex_recursive(np.copy(P))
|
|
251
|
+
|
|
252
|
+
for i in range(len(P)):
|
|
253
|
+
|
|
254
|
+
if not np.all(P[i] == P_proj[i]):
|
|
255
|
+
_R_t = scale_reference_directions(_R, scale)
|
|
256
|
+
_R_t = _R_t - (E[i] - P_proj[i])
|
|
257
|
+
|
|
258
|
+
if np.all(_R_t >= 0):
|
|
259
|
+
break
|
|
260
|
+
|
|
261
|
+
n_points += n_points_of_ref
|
|
262
|
+
R.append(_R_t)
|
|
263
|
+
|
|
264
|
+
# filter out points from to be removed from the original array
|
|
265
|
+
X = X[[i for i in I if i < len(X)]]
|
|
266
|
+
|
|
267
|
+
R = np.vstack(R)
|
|
268
|
+
|
|
269
|
+
return X, R
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
273
|
+
# Energy Functions
|
|
274
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def squared_dist(A, B):
|
|
278
|
+
return ((A[:, None] - B[None, :]) ** 2).sum(axis=2)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def calc_potential_energy(A, d):
|
|
282
|
+
i, j = triu_indices(len(A), 1)
|
|
283
|
+
D = sqrt(squared_dist(A, A)[i, j])
|
|
284
|
+
energy = log((1 / D ** d).mean())
|
|
285
|
+
return energy
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def calc_potential_energy_with_grad(x, d, return_mutual_dist=False):
|
|
289
|
+
diff = (x[:, None] - x[None, :])
|
|
290
|
+
# calculate the squared euclidean from each point to another
|
|
291
|
+
dist = np.sqrt((diff ** 2).sum(axis=2))
|
|
292
|
+
|
|
293
|
+
# make sure the distance to itself does not count
|
|
294
|
+
np.fill_diagonal(dist, np.inf)
|
|
295
|
+
|
|
296
|
+
# epsilon which is the smallest distance possible to avoid an overflow during gradient calculation
|
|
297
|
+
eps = 10 ** (-320 / (d + 2))
|
|
298
|
+
b = dist < eps
|
|
299
|
+
dist[b] = eps
|
|
300
|
+
|
|
301
|
+
# select only upper triangular matrix to have each mutual distance once
|
|
302
|
+
mutual_dist = dist[np.triu_indices(len(x), 1)]
|
|
303
|
+
|
|
304
|
+
# calculate the energy by summing up the squared distances
|
|
305
|
+
energy = (1 / mutual_dist ** d).sum()
|
|
306
|
+
log_energy = - np.log(len(mutual_dist)) + np.log(energy)
|
|
307
|
+
|
|
308
|
+
# calculate the gradient
|
|
309
|
+
grad = (-d * diff) / (dist ** (d + 2))[..., None]
|
|
310
|
+
grad = np.sum(grad, axis=1)
|
|
311
|
+
grad /= energy
|
|
312
|
+
|
|
313
|
+
ret = [log_energy, grad]
|
|
314
|
+
if return_mutual_dist:
|
|
315
|
+
ret.append(mutual_dist)
|
|
316
|
+
|
|
317
|
+
return tuple(ret)
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from pymoo.gradient.grad_autograd import value_and_grad, triu_indices, row_stack
|
|
6
|
+
from pymoo.util.normalization import normalize
|
|
7
|
+
from pymoo.util.ref_dirs.energy import squared_dist
|
|
8
|
+
from pymoo.util.ref_dirs.optimizer import Adam
|
|
9
|
+
from pymoo.util.reference_direction import ReferenceDirectionFactory, scale_reference_directions
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LayerwiseRieszEnergyReferenceDirectionFactory(ReferenceDirectionFactory):
|
|
13
|
+
|
|
14
|
+
def __init__(self,
|
|
15
|
+
n_dim,
|
|
16
|
+
partitions,
|
|
17
|
+
return_as_tuple=False,
|
|
18
|
+
n_max_iter=1000,
|
|
19
|
+
verbose=False,
|
|
20
|
+
X=None,
|
|
21
|
+
**kwargs):
|
|
22
|
+
|
|
23
|
+
super().__init__(n_dim, **kwargs)
|
|
24
|
+
self.scalings = None
|
|
25
|
+
self.n_max_iter = n_max_iter
|
|
26
|
+
self.verbose = verbose
|
|
27
|
+
self.return_as_tuple = return_as_tuple
|
|
28
|
+
self.X = X
|
|
29
|
+
self.partitions = partitions
|
|
30
|
+
|
|
31
|
+
def _step(self, optimizer, X, scalings):
|
|
32
|
+
obj, grad = value_and_grad(calc_potential_energy)(scalings, X)
|
|
33
|
+
scalings = optimizer.next(scalings, np.array(grad))
|
|
34
|
+
scalings = normalize(scalings, xl=0, xu=scalings.max())
|
|
35
|
+
return scalings, obj
|
|
36
|
+
|
|
37
|
+
def _solve(self, X, scalings):
|
|
38
|
+
|
|
39
|
+
# initialize the optimizer for the run
|
|
40
|
+
optimizer = Adam()
|
|
41
|
+
|
|
42
|
+
# for each iteration of gradient descent
|
|
43
|
+
for i in range(self.n_max_iter):
|
|
44
|
+
|
|
45
|
+
# execute one optimization step
|
|
46
|
+
_scalings, _obj = self._step(optimizer, X, scalings)
|
|
47
|
+
|
|
48
|
+
# evaluate how much the points have moved
|
|
49
|
+
delta = np.abs(_scalings - scalings).sum()
|
|
50
|
+
|
|
51
|
+
if self.verbose:
|
|
52
|
+
print(i, "objective", _obj, "delta", delta)
|
|
53
|
+
|
|
54
|
+
# if there was only a little delta during the last iteration -> terminate
|
|
55
|
+
if delta < 1e-5:
|
|
56
|
+
scalings = _scalings
|
|
57
|
+
break
|
|
58
|
+
|
|
59
|
+
# otherwise use the new points for the next iteration
|
|
60
|
+
scalings = _scalings
|
|
61
|
+
|
|
62
|
+
self.scalings = scalings
|
|
63
|
+
return get_points(X, scalings)
|
|
64
|
+
|
|
65
|
+
def do(self, **kwargs):
|
|
66
|
+
|
|
67
|
+
X = []
|
|
68
|
+
scalings = []
|
|
69
|
+
|
|
70
|
+
for k, p in enumerate(self.partitions):
|
|
71
|
+
|
|
72
|
+
if p > 1:
|
|
73
|
+
val = np.linspace(0, 1, p + 1)[1:-1]
|
|
74
|
+
_X = []
|
|
75
|
+
for i in range(self.n_dim):
|
|
76
|
+
for j in range(i + 1, self.n_dim):
|
|
77
|
+
x = np.zeros((len(val), self.n_dim))
|
|
78
|
+
x[:, i] = val
|
|
79
|
+
x[:, j] = 1 - val
|
|
80
|
+
_X.append(x)
|
|
81
|
+
|
|
82
|
+
X.append(np.vstack(_X + [np.eye(self.n_dim)]))
|
|
83
|
+
|
|
84
|
+
elif p == 1:
|
|
85
|
+
X.append(np.eye(self.n_dim))
|
|
86
|
+
else:
|
|
87
|
+
X.append(np.full(self.n_dim, 1 / self.n_dim)[None, :])
|
|
88
|
+
|
|
89
|
+
scalings.append(1 - k / len(self.partitions))
|
|
90
|
+
|
|
91
|
+
scalings = np.array(scalings)
|
|
92
|
+
X = self._solve(X, scalings)
|
|
93
|
+
|
|
94
|
+
return X
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
98
|
+
# Energy Functions
|
|
99
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def get_points(X, scalings):
|
|
103
|
+
vals = []
|
|
104
|
+
for i in range(len(X)):
|
|
105
|
+
vals.append(scale_reference_directions(X[i], scalings[i]))
|
|
106
|
+
X = row_stack(vals)
|
|
107
|
+
return X
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def calc_potential_energy(scalings, X):
|
|
111
|
+
X = get_points(X, scalings)
|
|
112
|
+
|
|
113
|
+
i, j = triu_indices(len(X), 1)
|
|
114
|
+
D = squared_dist(X, X)[i, j]
|
|
115
|
+
|
|
116
|
+
if np.any(D < 1e-12):
|
|
117
|
+
return np.nan, np.nan
|
|
118
|
+
|
|
119
|
+
return (1 / D).mean()
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from pymoo.algorithms.soo.nonconvex.ga import GA
|
|
2
|
+
from pymoo.core.problem import Problem
|
|
3
|
+
from pymoo.optimize import minimize
|
|
4
|
+
from pymoo.util.reference_direction import get_partition_closest_to_points, ReferenceDirectionFactory
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ReferenceDirectionGA(ReferenceDirectionFactory):
|
|
8
|
+
|
|
9
|
+
def __init__(self,
|
|
10
|
+
n_dim,
|
|
11
|
+
n_points,
|
|
12
|
+
fun,
|
|
13
|
+
pop_size=20,
|
|
14
|
+
n_gen=200,
|
|
15
|
+
verbose=False,
|
|
16
|
+
**kwargs):
|
|
17
|
+
|
|
18
|
+
super().__init__(n_dim, **kwargs)
|
|
19
|
+
|
|
20
|
+
self.n_points = n_points
|
|
21
|
+
self.pop_size = pop_size
|
|
22
|
+
self.n_gen = n_gen
|
|
23
|
+
|
|
24
|
+
self.fun = fun
|
|
25
|
+
self.verbose = verbose
|
|
26
|
+
|
|
27
|
+
def _do(self, random_state=None):
|
|
28
|
+
pop_size, n_gen = self.pop_size, self.n_gen
|
|
29
|
+
n_points, n_dim, = self.n_points, self.n_dim
|
|
30
|
+
fun = self.fun
|
|
31
|
+
|
|
32
|
+
class MyProblem(Problem):
|
|
33
|
+
|
|
34
|
+
def __init__(self):
|
|
35
|
+
self.n_points = n_points
|
|
36
|
+
self.n_dim = n_dim
|
|
37
|
+
self.n_partitions = get_partition_closest_to_points(n_points, n_dim)
|
|
38
|
+
|
|
39
|
+
super().__init__(n_var=n_points * n_dim,
|
|
40
|
+
n_obj=1,
|
|
41
|
+
xl=0.0,
|
|
42
|
+
xu=1.0,
|
|
43
|
+
elementwise_evaluation=True)
|
|
44
|
+
|
|
45
|
+
def get_points(self, x):
|
|
46
|
+
_x = x.reshape((self.n_points, self.n_dim)) ** 2
|
|
47
|
+
_x = _x / _x.sum(axis=1)[:, None]
|
|
48
|
+
return _x
|
|
49
|
+
|
|
50
|
+
def _evaluate(self, x, out, *args, **kwargs):
|
|
51
|
+
out["F"] = fun(self.get_points(x))
|
|
52
|
+
|
|
53
|
+
problem = MyProblem()
|
|
54
|
+
|
|
55
|
+
algorithm = GA(pop_size=pop_size, eliminate_duplicates=True)
|
|
56
|
+
|
|
57
|
+
res = minimize(problem,
|
|
58
|
+
algorithm,
|
|
59
|
+
termination=('n_gen', n_gen),
|
|
60
|
+
random_state=random_state,
|
|
61
|
+
verbose=True)
|
|
62
|
+
|
|
63
|
+
ref_dirs = problem.get_points(res.X)
|
|
64
|
+
return ref_dirs
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from pymoo.util.reference_direction import ReferenceDirectionFactory
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def check_n_points(n_points, n_dim):
|
|
7
|
+
"""
|
|
8
|
+
Returns n_partitions or a numeric value associated with the exception message.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
if n_dim == 1:
|
|
12
|
+
return [0]
|
|
13
|
+
|
|
14
|
+
I = n_dim * np.eye(n_dim)
|
|
15
|
+
W = np.zeros((1, n_dim))
|
|
16
|
+
edgeW = W
|
|
17
|
+
i = 0
|
|
18
|
+
|
|
19
|
+
while len(W) < n_points:
|
|
20
|
+
edgeW = np.tile(edgeW, (n_dim, 1)) + np.repeat(I, edgeW.shape[0], axis=0)
|
|
21
|
+
edgeW = np.unique(edgeW, axis=0)
|
|
22
|
+
edgeW = edgeW [np.any(edgeW == 0, axis=1)]
|
|
23
|
+
W = np.vstack((W + 1, edgeW))
|
|
24
|
+
i += 1
|
|
25
|
+
|
|
26
|
+
if len(W) == n_points:
|
|
27
|
+
return [i]
|
|
28
|
+
|
|
29
|
+
return [len(W) - len(edgeW), i - 1, len(W), i]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def incremental_lattice(n_partitions, n_dim):
|
|
33
|
+
I = n_dim * np.eye(n_dim)
|
|
34
|
+
W = np.zeros((1, n_dim))
|
|
35
|
+
edgeW = W
|
|
36
|
+
|
|
37
|
+
for _ in range(n_partitions):
|
|
38
|
+
edgeW = np.tile(edgeW, (n_dim, 1)) + np.repeat(I, edgeW.shape[0], axis=0)
|
|
39
|
+
edgeW = np.unique(edgeW, axis=0)
|
|
40
|
+
edgeW = edgeW [np.any(edgeW == 0, axis=1)]
|
|
41
|
+
W = np.vstack((W + 1, edgeW))
|
|
42
|
+
|
|
43
|
+
return W / (n_dim * n_partitions)
|
|
44
|
+
|
|
45
|
+
class IncrementalReferenceDirectionFactory(ReferenceDirectionFactory):
|
|
46
|
+
|
|
47
|
+
def __init__(self, n_dim, scaling=None, n_points=None, n_partitions=None, **kwargs) -> None:
|
|
48
|
+
super().__init__(n_dim, scaling=scaling, **kwargs)
|
|
49
|
+
|
|
50
|
+
if n_points is not None:
|
|
51
|
+
results = check_n_points(n_points, n_dim)
|
|
52
|
+
|
|
53
|
+
# the number of points are not matching to any partition number
|
|
54
|
+
if len(results) > 1:
|
|
55
|
+
raise Exception("The number of points (n_points = %s) can not be created uniformly.\n"
|
|
56
|
+
"Either choose n_points = %s (n_partitions = %s) or "
|
|
57
|
+
"n_points = %s (n_partitions = %s)." %
|
|
58
|
+
(n_points, results[0], results[1], results[2], results[3]))
|
|
59
|
+
|
|
60
|
+
self.n_partitions = results[0]
|
|
61
|
+
|
|
62
|
+
elif n_partitions is not None:
|
|
63
|
+
self.n_partitions = n_partitions
|
|
64
|
+
|
|
65
|
+
else:
|
|
66
|
+
raise Exception("Either provide number of partitions or number of points.")
|
|
67
|
+
|
|
68
|
+
def _do(self, **kwargs):
|
|
69
|
+
return incremental_lattice(self.n_partitions, self.n_dim)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def project_onto(y, p, n):
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
Project a point onto a plane.
|
|
8
|
+
|
|
9
|
+
Parameters
|
|
10
|
+
----------
|
|
11
|
+
y : The point that should be project
|
|
12
|
+
p : A point which lies on the plane
|
|
13
|
+
n : The normal vector of the plane
|
|
14
|
+
|
|
15
|
+
Returns
|
|
16
|
+
-------
|
|
17
|
+
|
|
18
|
+
proj_y : The projection onto the plane
|
|
19
|
+
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
is_1d = False
|
|
23
|
+
if len(y.shape) == 1:
|
|
24
|
+
is_1d = True
|
|
25
|
+
y = np.atleast_2d(y)
|
|
26
|
+
|
|
27
|
+
# make sure the plane vector is normalized
|
|
28
|
+
n = n / np.linalg.norm(n)
|
|
29
|
+
|
|
30
|
+
proj_y = y - n[None, :] * ((y - p) @ n)[:, None]
|
|
31
|
+
|
|
32
|
+
if is_1d:
|
|
33
|
+
proj_y = proj_y[0]
|
|
34
|
+
|
|
35
|
+
return proj_y
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def project_onto_sum_equals_one_plane(y):
|
|
39
|
+
n_dim = y.shape[-1]
|
|
40
|
+
return project_onto(y, np.eye(n_dim)[0], np.ones(n_dim))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def project_onto_sum_equals_zero_plane(y):
|
|
44
|
+
n_dim = y.shape[-1]
|
|
45
|
+
return project_onto(y, np.zeros(n_dim), np.ones(n_dim))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def project_onto_unit_simplex(X):
|
|
49
|
+
n_points, n_dim = X.shape
|
|
50
|
+
|
|
51
|
+
# a boolean mask of violating values - less than 0
|
|
52
|
+
I = X < 0
|
|
53
|
+
|
|
54
|
+
# get all the points which are out of bounds and need to be fixed
|
|
55
|
+
out_of_unit_simplex = np.where(I.sum(axis=1) > 0)[0]
|
|
56
|
+
|
|
57
|
+
# now check for each point if it is still in bound
|
|
58
|
+
for j in out_of_unit_simplex:
|
|
59
|
+
|
|
60
|
+
# indices where the last point was already out of bounds
|
|
61
|
+
subspace = np.logical_not(I[j])
|
|
62
|
+
|
|
63
|
+
# project the bounds back onto the simplex in the subspace
|
|
64
|
+
proj = np.zeros(n_dim)
|
|
65
|
+
proj[subspace] = project_onto_sum_equals_one_plane(X[j][subspace])
|
|
66
|
+
|
|
67
|
+
# set the result to the corresponding value
|
|
68
|
+
X[j] = proj
|
|
69
|
+
|
|
70
|
+
test1 = matrix_project_onto_sum_equals_one_plane(X[j][subspace])
|
|
71
|
+
test2 = project_onto_sum_equals_one_plane(X[j][subspace])
|
|
72
|
+
|
|
73
|
+
if not np.allclose(test1, test2):
|
|
74
|
+
print("test")
|
|
75
|
+
|
|
76
|
+
if np.any(X[j] < 0):
|
|
77
|
+
print("test")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def project_onto_unit_simplex_recursive(X):
|
|
81
|
+
# get all the points which are out of bounds and need to be fixed
|
|
82
|
+
out_of_unit_simplex = np.where(np.any(X < 0, axis=1))[0]
|
|
83
|
+
|
|
84
|
+
# now check for each point if it is still in bound
|
|
85
|
+
for j in out_of_unit_simplex:
|
|
86
|
+
|
|
87
|
+
while True:
|
|
88
|
+
X[j, X[j] < 0] = 0
|
|
89
|
+
|
|
90
|
+
# indices where the last point was already out of bounds
|
|
91
|
+
subspace = X[j] > 0
|
|
92
|
+
|
|
93
|
+
# project the bounds back onto the simplex in the subspace
|
|
94
|
+
X[j, subspace] = project_onto_sum_equals_one_plane(X[j][subspace])
|
|
95
|
+
|
|
96
|
+
if np.all(X[j] >= 0):
|
|
97
|
+
break
|
|
98
|
+
|
|
99
|
+
return X
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def matrix_project_onto_sum_equals_one_plane(next):
|
|
103
|
+
n_dim = len(next)
|
|
104
|
+
P, S = np.eye(n_dim)[0], next
|
|
105
|
+
|
|
106
|
+
# create for each subspace dimension a point on the hyperplane
|
|
107
|
+
points = P + np.eye(n_dim)[1:]
|
|
108
|
+
points /= points.sum(axis=1)[:, None]
|
|
109
|
+
|
|
110
|
+
v = points - P
|
|
111
|
+
s = S - P
|
|
112
|
+
|
|
113
|
+
# solve a system of linear equations to project the point
|
|
114
|
+
A = np.zeros((n_dim - 1, n_dim - 1))
|
|
115
|
+
for i in range(n_dim - 1):
|
|
116
|
+
for j in range(n_dim - 1):
|
|
117
|
+
A[i, j] = np.dot(v[i], v[j])
|
|
118
|
+
|
|
119
|
+
b = np.zeros(n_dim - 1)
|
|
120
|
+
for i in range(n_dim - 1):
|
|
121
|
+
b[i] = np.dot(s, v[i])
|
|
122
|
+
|
|
123
|
+
x = np.linalg.solve(A, b)
|
|
124
|
+
|
|
125
|
+
# finally calculate the projection onto the plane
|
|
126
|
+
proj = (P + x @ v)
|
|
127
|
+
|
|
128
|
+
return proj
|