Skip to content

Commit ad98c14

Browse files
authored
Fix for issue #620 regarding AGE-MOEA-II (#635)
* Fix overflow and division by zero errors: Add checks to prevent division by zero and apply regularization to avoid extremely large results in exponentiation when the base is too small. * Add example for AGE-MOEA-II with constrained problems * Fix NumbaDeprecationWarning in AGE-MOEA and AGE-MOEA-II * Add additional checks for Inf and NaN values * Fix more overflow errors * Adding more overflow checks * Fixing division-by-zero errors in survival_score(..)
1 parent e00e3c1 commit ad98c14

File tree

3 files changed

+119
-19
lines changed

3 files changed

+119
-19
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from pymoo.indicators.igd import IGD
2+
from pymoo.util.ref_dirs import get_reference_directions
3+
from pymoo.algorithms.moo.age2 import AGEMOEA2
4+
from pymoo.optimize import minimize
5+
6+
from pymoo.problems.many import C1DTLZ1, DC1DTLZ1, DC1DTLZ3, DC2DTLZ1, DC2DTLZ3, DC3DTLZ1, DC3DTLZ3, C1DTLZ3, \
7+
C2DTLZ2, C3DTLZ1, C3DTLZ4
8+
import ray
9+
import numpy as np
10+
11+
benchmark_algorithms = [
12+
AGEMOEA2(),
13+
]
14+
15+
benchmark_problems = [
16+
C1DTLZ1, DC1DTLZ1, DC1DTLZ3, DC2DTLZ1, DC2DTLZ3, DC3DTLZ1, DC3DTLZ3, C1DTLZ3, C2DTLZ2, C3DTLZ1, C3DTLZ4
17+
]
18+
19+
20+
def run_benchmark(problem_class, algorithm):
21+
# Instantiate the problem
22+
problem = problem_class()
23+
24+
res = minimize(
25+
problem,
26+
algorithm,
27+
pop_size=100,
28+
verbose=True,
29+
seed=1,
30+
termination=('n_gen', 2000)
31+
)
32+
33+
# Step 4: Generate the reference points
34+
ref_dirs = get_reference_directions("uniform", problem.n_obj, n_points=528)
35+
36+
# Obtain the true Pareto front (for synthetic problems)
37+
pareto_front = problem.pareto_front(ref_dirs)
38+
39+
# Calculate IGD
40+
if res.F is None:
41+
igd = np.Infinity
42+
else:
43+
igd = IGD(pareto_front)(res.F)
44+
45+
result = {
46+
"problem": problem,
47+
"algorithm": algorithm,
48+
"result": res,
49+
"igd": igd
50+
}
51+
52+
return result
53+
54+
55+
tasks = []
56+
for problem in benchmark_problems:
57+
for algorithm in benchmark_algorithms:
58+
tasks.append(ray.remote(run_benchmark).remote(problem, algorithm))
59+
result = ray.get(tasks)
60+
61+
for res in result:
62+
print(f"Algorithm = {res['algorithm'].__class__.__name__}, "
63+
f"Problem = {res['problem'].__class__.__name__}, "
64+
f"IGD = {res['igd']}")

pymoo/algorithms/moo/age.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,13 @@ def survival_score(self, front, ideal_point):
167167
p = self.compute_geometry(front, extreme, n)
168168

169169
nn = np.linalg.norm(front, p, axis=1)
170-
distances = self.pairwise_distances(front, p) / (nn[:, None])
170+
# Replace very small norms with 1 to prevent division by zero
171+
nn[nn < 1e-8] = 1
172+
173+
distances = self.pairwise_distances(front, p)
174+
distances[distances < 1e-8] = 1e-8 # Replace very small entries to prevent division by zero
175+
176+
distances = distances / (nn[:, None])
171177

172178
neighbors = 2
173179
remaining = np.arange(m)
@@ -209,7 +215,7 @@ def compute_geometry(front, extreme, n):
209215
return p
210216

211217
@staticmethod
212-
@jit(fastmath=True)
218+
#@jit(nopython=True, fastmath=True)
213219
def pairwise_distances(front, p):
214220
m = np.shape(front)[0]
215221
distances = np.zeros((m, m))
@@ -219,7 +225,7 @@ def pairwise_distances(front, p):
219225
return distances
220226

221227
@staticmethod
222-
@jit(fastmath=True)
228+
@jit(nopython=True, fastmath=True)
223229
def minkowski_distances(A, B, p):
224230
m1 = np.shape(A)[0]
225231
m2 = np.shape(B)[0]
@@ -254,7 +260,7 @@ def find_corner_solutions(front):
254260
return indexes
255261

256262

257-
@jit(fastmath=True)
263+
@jit(nopython=True, fastmath=True)
258264
def point_2_line_distance(P, A, B):
259265
d = np.zeros(P.shape[0])
260266

pymoo/algorithms/moo/age2.py

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -64,48 +64,78 @@ def __init__(self,
6464
self.tournament_type = 'comp_by_rank_and_crowding'
6565

6666

67-
@jit(fastmath=True)
67+
@jit(nopython=True, fastmath=True)
6868
def project_on_manifold(point, p):
6969
dist = sum(point[point > 0] ** p) ** (1/p)
7070
return np.multiply(point, 1 / dist)
7171

7272

73+
import numpy as np
74+
75+
7376
def find_zero(point, n, precision):
7477
x = 1
75-
78+
epsilon = 1e-10 # Small constant for regularization
7679
past_value = x
80+
max_float = np.finfo(np.float64).max # Maximum representable float value
81+
log_max_float = np.log(max_float) # Logarithm of the maximum float
82+
7783
for i in range(0, 100):
7884

79-
# Original function
85+
# Original function with regularization
8086
f = 0.0
8187
for obj_index in range(0, n):
8288
if point[obj_index] > 0:
83-
f += np.power(point[obj_index], x)
89+
log_value = x * np.log(point[obj_index] + epsilon)
90+
if log_value < log_max_float:
91+
f += np.exp(log_value)
92+
else:
93+
return 1 # Handle overflow by returning a fallback value
8494

85-
f = np.log(f)
95+
f = np.log(f) if f > 0 else 0 # Avoid log of non-positive numbers
8696

87-
# Derivative
97+
# Derivative with regularization
8898
numerator = 0
8999
denominator = 0
90100
for obj_index in range(0, n):
91101
if point[obj_index] > 0:
92-
numerator = numerator + np.power(point[obj_index], x) * np.log(point[obj_index])
93-
denominator = denominator + np.power(point[obj_index], x)
94-
95-
if denominator == 0:
96-
return 1
102+
log_value = x * np.log(point[obj_index] + epsilon)
103+
if log_value < log_max_float:
104+
power_value = np.exp(log_value)
105+
log_term = np.log(point[obj_index] + epsilon)
106+
107+
# Use logarithmic comparison to avoid overflow
108+
if log_value + np.log(abs(log_term) + epsilon) < log_max_float:
109+
result = power_value * log_term
110+
numerator += result
111+
denominator += power_value
112+
else:
113+
# Handle extreme cases by capping the result
114+
numerator += max_float
115+
denominator += power_value
116+
else:
117+
return 1 # Handle overflow by returning a fallback value
118+
119+
if denominator == 0 or np.isnan(denominator) or np.isinf(denominator):
120+
return 1 # Handle division by zero or NaN
121+
122+
if np.isnan(numerator) or np.isinf(numerator):
123+
return 1 # Handle invalid numerator
97124

98125
ff = numerator / denominator
99126

100-
# zero of function
127+
if ff == 0: # Check for zero before division
128+
return 1 # Handle by returning a fallback value
129+
130+
# Update x using Newton's method
101131
x = x - f / ff
102132

103133
if abs(x - past_value) <= precision:
104134
break
105135
else:
106-
paste_value = x # update current point
136+
past_value = x # Update current point
107137

108-
if isinstance(x, complex):
138+
if isinstance(x, complex) or np.isinf(x) or np.isnan(x):
109139
return 1
110140
else:
111141
return x
@@ -135,7 +165,7 @@ def compute_geometry(front, extreme, n):
135165
return p
136166

137167
@staticmethod
138-
@jit(fastmath=True)
168+
@jit(nopython=True, fastmath=True)
139169
def pairwise_distances(front, p):
140170
m, n = front.shape
141171
projected_front = front.copy()

0 commit comments

Comments
 (0)