diff --git a/optuna/samplers/nsgaii/_constraints_evaluation.py b/optuna/samplers/nsgaii/_constraints_evaluation.py index f7e14638c9..33f3b998e5 100644 --- a/optuna/samplers/nsgaii/_constraints_evaluation.py +++ b/optuna/samplers/nsgaii/_constraints_evaluation.py @@ -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) diff --git a/optuna/samplers/nsgaii/_elite_population_selection_strategy.py b/optuna/samplers/nsgaii/_elite_population_selection_strategy.py index bf416267ae..5920b95715 100644 --- a/optuna/samplers/nsgaii/_elite_population_selection_strategy.py +++ b/optuna/samplers/nsgaii/_elite_population_selection_strategy.py @@ -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 diff --git a/optuna/study/_multi_objective.py b/optuna/study/_multi_objective.py index 71eb465288..976e7517ec 100644 --- a/optuna/study/_multi_objective.py +++ b/optuna/study/_multi_objective.py @@ -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