11import numpy as np
2+ from scipy .spatial .ckdtree import cKDTree
23
34
45class DecisionMaking :
@@ -13,7 +14,7 @@ def do(self, F, *args, **kwargs):
1314
1415
1516def normalize (F , ideal_point = None , nadir_point = None , estimate_bounds_if_none = True , return_bounds = False ):
16- N = np . copy ( F )
17+ N = F . astype ( np . float )
1718
1819 if estimate_bounds_if_none :
1920 if ideal_point is None :
@@ -43,3 +44,91 @@ def normalize(F, ideal_point=None, nadir_point=None, estimate_bounds_if_none=Tru
4344 return N , norm , ideal_point , nadir_point
4445 else :
4546 return N
47+
48+
49+ class NeighborFinder :
50+
51+ def __init__ (self , N ,
52+ epsilon = 0.125 ,
53+ n_neighbors = None ,
54+ n_min_neigbors = None ,
55+ consider_2d = True ):
56+
57+ super ().__init__ ()
58+ self .N = N
59+ self .consider_2d = consider_2d
60+
61+ _ , n_dim = N .shape
62+
63+ # at least find dimensionality times two neighbors - if enabled
64+ if n_min_neigbors == "auto" :
65+ self .n_min_neigbors = 2 * n_dim
66+
67+ # disable the minimum neighbor variable
68+ else :
69+ self .n_min_neigbors = np .inf
70+
71+ # either choose epsilon
72+ self .epsilon = epsilon
73+
74+ # if none choose the number of neighbors
75+ self .n_neighbors = n_neighbors
76+
77+ if self .N .shape [1 ] == 1 :
78+ raise Exception ("At least 2 objectives must be provided." )
79+
80+ elif self .consider_2d and self .N .shape [1 ] == 2 :
81+ self .min , self .max = N .min (), N .max ()
82+ self .rank = np .argsort (N [:, 0 ])
83+ self .pos_in_rank = np .argsort (self .rank )
84+
85+ else :
86+ self .tree = cKDTree (N )
87+
88+ def find (self , i ):
89+
90+ if self .consider_2d and self .N .shape [1 ] == 2 :
91+ neighbours = []
92+
93+ pos = self .pos_in_rank [i ]
94+ if pos > 0 :
95+ neighbours .append (self .rank [pos - 1 ])
96+ if pos < len (self .N ) - 1 :
97+ neighbours .append (self .rank [pos + 1 ])
98+
99+ else :
100+
101+ # for each neighbour in a specific radius of that solution
102+ if self .epsilon is not None :
103+ neighbours = self .tree .query_ball_point ([self .N [i ]], self .epsilon ).tolist ()[0 ]
104+ elif self .n_neighbors is not None :
105+ neighbours = self .tree .query ([self .N [i ]], k = self .n_neighbors + 1 )[1 ].tolist ()[0 ]
106+ else :
107+ raise Exception ("Either define epsilon or number of neighbors." )
108+
109+ # in case n_min_neigbors is enabled
110+ if len (neighbours ) < self .n_min_neigbors :
111+ neighbours = self .tree .query ([self .N [i ]], k = self .n_min_neigbors + 1 )[1 ].tolist ()[0 ]
112+
113+ return neighbours
114+
115+
116+ def find_outliers_upper_tail (mu ):
117+
118+ # remove values that are nan
119+ I = np .where (np .logical_and (np .logical_not (np .isnan (mu )), np .logical_not (np .isinf (mu ))))[0 ]
120+ mu = mu [I ]
121+
122+ # calculate mean and sigma
123+ mean , sigma = mu .mean (), mu .std ()
124+
125+ # calculate the deviation in terms of sigma
126+ deviation = (mu - mean ) / sigma
127+
128+ # 2 * sigma is considered as an outlier
129+ S = I [np .where (deviation >= 2 )[0 ]]
130+
131+ if len (S ) == 0 and deviation .max () > 1 :
132+ S = I [[np .argmax (mu )]]
133+
134+ return S if len (S ) > 0 else None
0 commit comments