Skip to content

Commit

Permalink
Merge pull request #68 from iraikov/fix/non_surrogate
Browse files Browse the repository at this point in the history
Fixes to non-surrogate regime
  • Loading branch information
iraikov committed Dec 26, 2023
2 parents 88ae403 + 41d6c0e commit 459966c
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 100 deletions.
2 changes: 1 addition & 1 deletion dmosopt/CMAES.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ def get_population_strategy(self):
population_parm, population_obj
)
population_parm, population_obj, _ = remove_worst(
population_parm, population_obj, self.popsize, self.nInput, self.nOutput
population_parm, population_obj, self.popsize
)

return population_parm, population_obj
Expand Down
21 changes: 11 additions & 10 deletions dmosopt/MOASMO.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def optimize(
else:
logger.info(f"{optimizer.name}: generation {i} of {num_generations}...")

## optimizer generate-update
## optimizer generate-update
x_gen, state_gen = optimizer.generate()

if model is None:
Expand Down Expand Up @@ -319,7 +319,7 @@ def epoch(
)

try:
res = next(opt_gen)
item = next(opt_gen)
except StopIteration as ex:
opt_gen.close()
opt_gen = None
Expand All @@ -330,14 +330,17 @@ def epoch(
x = res.x
y = res.y
else:
x_gen = item
while True:
x_gen = res

y_gen, c_gen = None, None
if sm is not None:
y_gen = sm.evaluate(x_gen)
else:
_, y_gen, c_gen = yield x_gen
item_eval = yield x_gen, True
_, y_gen, c_gen = item_eval

res = None
try:
res = opt_gen.send(y_gen)
except StopIteration as ex:
Expand All @@ -350,6 +353,8 @@ def epoch(
x = res.x
y = res.y
break
else:
x_gen = res

if surrogate_method_name is not None:
D = MOEA.crowding_distance(best_y)
Expand Down Expand Up @@ -517,9 +522,7 @@ def get_best(
if c is not None:
c = c[~is_duplicate]

xtmp, ytmp, rank, _, perm = MOEA.sortMO(
xtmp, ytmp, nInput, nOutput, return_perm=True
)
xtmp, ytmp, rank, _, perm = MOEA.sortMO(xtmp, ytmp, return_perm=True)
idxp = rank == 0
best_x = xtmp[idxp, :]
best_y = ytmp[idxp, :]
Expand Down Expand Up @@ -559,9 +562,7 @@ def get_feasible(x, y, f, c, nInput, nOutput, epochs=None):
else:
feasible = None

perm_x, perm_y, rank, _, perm = MOEA.sortMO(
xtmp, ytmp, nInput, nOutput, return_perm=True
)
perm_x, perm_y, rank, _, perm = MOEA.sortMO(xtmp, ytmp, return_perm=True)
# x, y are already permutated upon return
perm_f = f[perm]
perm_epoch = epochs[perm]
Expand Down
81 changes: 60 additions & 21 deletions dmosopt/MOEA.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,26 +239,22 @@ def crossover_sbx(local_random, parent1, parent2, di_crossover, xlb, xub, nchild
def sortMO(
x,
y,
nInput,
nOutput,
return_perm=False,
x_distance_metrics=None,
y_distance_metrics=["crowding"],
y_distance_metrics=None,
):
"""Non-dominated sort for multi-objective optimization
x: input parameter matrix
y: output objectives matrix
nInput: number of input
nOutput: number of output
return_perm: if True, return permutation indices of original input
"""
y_distance_functions = [crowding_distance]
y_distance_functions = []
if y_distance_metrics is not None:
y_distance_functions = []
assert len(y_distance_metrics) > 0
for distance_metric in y_distance_metrics:
if distance_metric == None:
y_distance_functions.append(crowding_distance)
if callable(distance_metric):
y_distance_functions.append(distance_metric)
elif distance_metric == "crowding":
y_distance_functions.append(crowding_distance)
elif distance_metric == "euclidean":
Expand All @@ -280,15 +276,10 @@ def sortMO(

y_dists = list([np.zeros_like(rank) for _ in y_distance_functions])
x_dists = list([np.zeros_like(rank) for _ in x_distance_functions])
rmax = int(rank.max())
for front in range(rmax + 1):
rankidx = rank == front
for i, y_distance_function in enumerate(y_distance_functions):
D = y_distance_function(y[rankidx, :])
y_dists[i][rankidx] = D
for i, x_distance_function in enumerate(x_distance_functions):
D = x_distance_function(x[rankidx, :])
x_dists[i][rankidx] = D
for i, y_distance_function in enumerate(y_distance_functions):
y_dists[i] = y_distance_function(y)
for i, x_distance_function in enumerate(x_distance_functions):
x_dists[i] = x_distance_function(x)

perm = np.lexsort(
(list([-dist for dist in x_dists]) + list([-dist for dist in y_dists]) + [rank])
Expand All @@ -305,6 +296,58 @@ def sortMO(
return x, y, rank, y_dists


def orderMO(
x,
y,
x_distance_metrics=None,
y_distance_metrics=None,
):
"""Returns the ordering for a non-dominated sort for multi-objective optimization
x: input parameter matrix
y: output objectives matrix
"""
y_distance_functions = []
if y_distance_metrics is not None:
assert len(y_distance_metrics) > 0
for distance_metric in y_distance_metrics:
if callable(distance_metric):
y_distance_functions.append(distance_metric)
elif distance_metric == "crowding":
y_distance_functions.append(crowding_distance)
elif distance_metric == "euclidean":
y_distance_functions.append(euclidean_distance)
elif callable(distance_metric):
y_distance_functions.append(distance_metric)
else:
raise RuntimeError(f"sortMO: unknown distance metric {distance_metric}")

x_distance_functions = []
if x_distance_metrics is not None:
for distance_metric in x_distance_metrics:
if callable(distance_metric):
x_distance_functions.append(distance_metric)
else:
raise RuntimeError(f"sortMO: unknown distance metric {distance_metric}")

rank = dda_non_dominated_sort(y)

y_dists = list([np.zeros_like(rank) for _ in y_distance_functions])
x_dists = list([np.zeros_like(rank) for _ in x_distance_functions])
for i, y_distance_function in enumerate(y_distance_functions):
y_dists[i] = y_distance_function(y)
for i, x_distance_function in enumerate(x_distance_functions):
x_dists[i] = x_distance_function(x)

perm = np.lexsort(
(list([-dist for dist in x_dists]) + list([-dist for dist in y_dists]) + [rank])
)

rank = rank[perm]
y_dists = tuple([dist[perm] for dist in y_dists])

return perm, rank, y_dists


def crowding_distance(Y):
"""Crowding distance metric.
Y is the output data matrix
Expand Down Expand Up @@ -385,17 +428,13 @@ def remove_worst(
population_parm,
population_obj,
pop,
nInput,
nOutput,
x_distance_metrics=None,
y_distance_metrics=None,
):
"""remove the worst individuals in the population"""
population_parm, population_obj, rank, _ = sortMO(
population_parm,
population_obj,
nInput,
nOutput,
x_distance_metrics=x_distance_metrics,
y_distance_metrics=y_distance_metrics,
)
Expand Down
4 changes: 0 additions & 4 deletions dmosopt/NSGA2.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,6 @@ def initialize_state(
x, y, rank, _ = sortMO(
x,
y,
self.nInput,
self.nOutput,
x_distance_metrics=self.x_distance_metrics,
y_distance_metrics=self.y_distance_metrics,
)
Expand Down Expand Up @@ -188,8 +186,6 @@ def update_strategy(
population_parm,
population_obj,
popsize,
nInput,
nOutput,
x_distance_metrics=self.x_distance_metrics,
y_distance_metrics=self.y_distance_metrics,
)
Expand Down
6 changes: 0 additions & 6 deletions dmosopt/SMPSO.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,6 @@ def initialize_state(
xs[p], ys[p], rank_p, _ = sortMO(
xs[p],
ys[p],
nInput,
nOutput,
x_distance_metrics=self.x_distance_metrics,
y_distance_metrics=self.y_distance_metrics,
)
Expand Down Expand Up @@ -216,8 +214,6 @@ def update_strategy(
population_parm_p,
population_obj_p,
popsize,
nInput,
nOutput,
x_distance_metrics=self.x_distance_metrics,
y_distance_metrics=self.y_distance_metrics,
)
Expand All @@ -235,8 +231,6 @@ def get_population_strategy(self):
pop_parm,
pop_obj,
popsize,
nInput,
nOutput,
x_distance_metrics=self.x_distance_metrics,
y_distance_metrics=self.y_distance_metrics,
)
Expand Down
41 changes: 5 additions & 36 deletions dmosopt/TRS.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from dmosopt.MOEA import (
Struct,
MOEA,
orderMO,
remove_worst,
remove_duplicates,
)
Expand Down Expand Up @@ -79,7 +80,7 @@ def initialize_state(
local_random: Optional[np.random.Generator] = None,
**params,
):
order, rank = sortMO(x, y, self.x_distance_metrics)
order, rank = orderMO(x, y, x_distance_metrics=self.x_distance_metrics)
population_parm = x[order][: self.popsize]
population_obj = y[order][: self.popsize]
rank = rank[: self.popsize]
Expand Down Expand Up @@ -197,7 +198,9 @@ def select_candidates(self, candidates_x, candidates_y):
candidates_inds, dtype=bool
)

order, rank = sortMO(candidates_x, candidates_y, self.x_distance_metrics)
order, rank = orderMO(
candidates_x, candidates_y, x_distance_metrics=self.x_distance_metrics
)

chosen = np.zeros_like(candidates_inds, dtype=bool)
not_chosen = np.zeros_like(candidates_inds, dtype=bool)
Expand Down Expand Up @@ -281,37 +284,3 @@ def restart_state(self):
self.state.tr.length = self.state.tr.length_init
self.state.tr.Y_best = np.asarray([np.inf] * self.state.tr.dim).reshape((1, -1))
self.state.tr.restart = False


def sortMO(
x,
y,
x_distance_metrics=None,
):
"""Non-dominated sort for multi-objective optimization
x: input parameter matrix
y: output objectives matrix
"""

x_distance_functions = []
if x_distance_metrics is not None:
for distance_metric in x_distance_metrics:
if callable(distance_metric):
x_distance_functions.append(distance_metric)
else:
raise RuntimeError(f"sortMO: unknown distance metric {distance_metric}")

rank = dda_non_dominated_sort(y)

x_dists = list([np.zeros_like(rank) for _ in x_distance_functions])
rmax = int(rank.max())
if len(x_dists) > 0:
for front in range(rmax + 1):
rankidx = rank == front
for i, x_distance_function in enumerate(x_distance_functions):
D = x_distance_function(x[rankidx, :])
x_dists[i][rankidx] = D

perm = np.lexsort((list([-dist for dist in x_dists]) + [rank]))

return perm, rank
Loading

0 comments on commit 459966c

Please sign in to comment.