Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions optuna/samplers/nsgaii/_constraints_evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,15 @@ def _evaluate_penalty(population: Sequence[FrozenTrial]) -> np.ndarray:
feasible/infeasible and None means that the trial does not have constraint values.
"""

penalty: list[float] = []
for trial in population:
constraints = trial.system_attrs.get(_CONSTRAINTS_KEY)
if constraints is None:
penalty.append(np.nan)
else:
penalty.append(sum(v for v in constraints if v > 0))
# Use list comprehension for better performance
penalty = [
(
np.nan
if (constraints := trial.system_attrs.get(_CONSTRAINTS_KEY)) is None
else sum(v for v in constraints if v > 0)
)
for trial in population
]
return np.array(penalty)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,9 @@ def _rank_population(
penalty = _evaluate_penalty(population) if is_constrained else None

domination_ranks = _fast_non_domination_rank(objective_values, penalty=penalty)
population_per_rank: list[list[FrozenTrial]] = [[] for _ in range(max(domination_ranks) + 1)]
num_ranks = domination_ranks.max() + 1
population_per_rank: list[list[FrozenTrial]] = [[] for _ in range(num_ranks)]
# Use more efficient grouping via preallocated lists and bulk push
for trial, rank in zip(population, domination_ranks):
if rank == -1:
continue
Expand Down
36 changes: 23 additions & 13 deletions optuna/study/_multi_objective.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,25 +97,35 @@ def _fast_non_domination_rank(

ranks = np.full(len(loss_values), -1, dtype=int)
is_penalty_nan = np.isnan(penalty)
is_feasible = np.logical_and(~is_penalty_nan, penalty <= 0)
is_infeasible = np.logical_and(~is_penalty_nan, penalty > 0)
is_feasible = (~is_penalty_nan) & (penalty <= 0)
is_infeasible = (~is_penalty_nan) & (penalty > 0)

# First, we calculate the domination rank for feasible trials.
ranks[is_feasible] = _calculate_nondomination_rank(loss_values[is_feasible], n_below=n_below)
n_below -= int(np.count_nonzero(is_feasible))
feasible_count = np.count_nonzero(is_feasible)
if feasible_count:
feasible_ranks = _calculate_nondomination_rank(loss_values[is_feasible], n_below=n_below)
ranks[is_feasible] = feasible_ranks
n_below -= feasible_count

# Second, we calculate the domination rank for infeasible trials.
top_rank_infeasible = np.max(ranks[is_feasible], initial=-1) + 1
ranks[is_infeasible] = top_rank_infeasible + _calculate_nondomination_rank(
penalty[is_infeasible][:, np.newaxis], n_below=n_below
)
n_below -= int(np.count_nonzero(is_infeasible))
infeasible_count = np.count_nonzero(is_infeasible)
if infeasible_count:
top_rank_infeasible = np.max(ranks[is_feasible], initial=-1) + 1
infeasible_ranks = _calculate_nondomination_rank(
penalty[is_infeasible][:, np.newaxis], n_below=n_below
)
ranks[is_infeasible] = top_rank_infeasible + infeasible_ranks
n_below -= infeasible_count

# Third, we calculate the domination rank for trials with no penalty information.
top_rank_penalty_nan = np.max(ranks[~is_penalty_nan], initial=-1) + 1
ranks[is_penalty_nan] = top_rank_penalty_nan + _calculate_nondomination_rank(
loss_values[is_penalty_nan], n_below=n_below
)
penalty_nan_count = np.count_nonzero(is_penalty_nan)
if penalty_nan_count:
top_rank_penalty_nan = np.max(ranks[~is_penalty_nan], initial=-1) + 1
penalty_nan_ranks = _calculate_nondomination_rank(
loss_values[is_penalty_nan], n_below=n_below
)
ranks[is_penalty_nan] = top_rank_penalty_nan + penalty_nan_ranks

assert np.all(ranks != -1), "All the rank must be updated."
return ranks

Expand Down