Skip to content

Utilities

Correctness fixes in metrics, constraint penalties, decoding, and sorting networks.


nsga_diversity depended on list order

Deb's \(\Delta\) is defined on a front sorted along one objective. The function used adjacent list entries as-is, so a permutation of the same points changed \(\Delta\) (perfectly uniform \(0\) vs \(0.5\)).

Fix. Sort a copy of the front by the first objective before computing \(d_f\), \(d_l\), and \(d_t\).

Validator. tests/test_various/test_nsga_metrics.py::test_nsga_diversity_is_invariant_to_front_order


Single-point front returned \(d_f + d_l\) instead of \(\Delta = 1\)

With \(N=1\) both consecutive-distance terms vanish and Deb's formula is \((d_f+d_l)/(d_f+d_l) = 1\). The code returned the unnormalized numerator (for example \(\sqrt{2}\)).

Fix. Return 1.0 when len(population) == 1.

Validator. tests/test_various/test_nsga_metrics.py::test_nsga_diversity_for_a_single_point_is_one


Collapsed multi-point front divided by zero

With \(N \ge 2\) identical points and both extremes at that location, every consecutive distance is \(0\), so \(d_m = 0\) and \(d_f + d_l + N_{\mathrm{gaps}} d_m = 0\). Deb's \(\Delta\) is \(0/0\). The \(N=1\) path already returns \(1\); two copies of the same point raised ZeroDivisionError.

Fix. Return 1.0 when the denominator is \(0\).

Validator. tests/test_various/test_nsga_metrics.py::test_nsga_diversity_collapsed_front_is_one


DeltaPenalty treated an ndarray as a scalar

numpy.ndarray is not a collections.abc.Sequence, so a per-objective vector went through itertools.repeat. The penalty became a tuple of arrays and Fitness.values raised TypeError.

Fix. Treat an ndarray delta or distance as a per-objective sequence (numpy.asarray + iterate).

Validator. tests/test_utilities/test_constraints.py::test_delta_penalty_accepts_ndarray_delta_and_distance


ClosestValidPenalty had the same repeat() wrap

Its distance check used the identical isinstance(..., Sequence) test.

Fix. Same ndarray handling as DeltaPenalty.

Validator. tests/test_various/test_constraints.py::test_closest_valid_penalty_accepts_ndarray_distance


bin2float decoded True/False as "True"/"False"

int("".join(map(str, values)), 2) raised ValueError: invalid literal ... 'TrueTrue'. mut_flip_bit preserves bool, so a boolean individual crashed on evaluate.

Fix. Coerce each bit with int(bool(v)) before joining.

Validator. tests/test_utilities/test_bm_decors.py::test_bin2float_decodes_boolean_bits_like_integers


SortingNetwork.evaluate was binary-only

Integer cases were scored against a bitstring lookup (ordered[sum(seq)]) or crashed. Provided cases must be compared to sorted(original).

Fix. Copy each case, sort the copy, and compare it to sorted(original). Default bitstring evaluate() still reports 0 on a correct network.

Validator. tests/test_various/test_sorting_network.py::test_sorting_network_evaluate_provided_cases_against_sorted_original


duplicate_count crashed on NumPy individuals

NumPy arrays are unhashable, so the set scan raised TypeError as intended. The sort fallback then called sorted(keys), and ndarray comparison raises ValueError (ambiguous truth value) instead of TypeError. The list-membership fallback would hit the same error on value not in unique.

Fix. Hash an ndarray key by (shape, dtype, tobytes()) so the set scan works. Two equal genomes count as one distinct key.

Validator. tests/test_various/test_metrics.py::test_duplicate_count_ndarray_individuals_count_twins


NSGA metrics read genomes instead of fitness for reference points

nsga_convergence scores the front with ind.fitness.values but built the true front as tuple(opt). For a list individual that is the genome. nsga_diversity did the same for first / last via first[0]. The signatures take Individual; passing a front of individuals with genes that differ from objectives silently returned the wrong \(\Delta\) / \(\Upsilon\).

Fix. Read fitness.values when the point has a non-empty fitness; otherwise keep the raw-vector path used by existing tests.

Validators.

  • tests/test_various/test_nsga_metrics.py::test_nsga_convergence_uses_fitness_when_optimal_are_individuals
  • tests/test_various/test_nsga_metrics.py::test_nsga_diversity_uses_fitness_when_extremes_are_individuals

inv_gen_dist read genomes instead of fitness

nsga_convergence scores fronts with fitness.values but IGD used list(ind). For a list individual that is the genome. A front of individuals whose genes differ from objectives silently returned the wrong distance (often \(0\) when every genome was overwritten to the same dummy values).

Fix. Build both point sets with the same _objective_row helper: fitness.values when present and non-empty, otherwise the raw-vector path used by existing tests.

Validator. tests/test_various/test_nsga_metrics.py::test_inv_gen_dist_uses_fitness_when_sets_are_individuals


sort_non_dominated crashed on invalid or mixed fitness

Building numpy.array([ind.fitness.wvalues ...]) raised ValueError when some individuals were unevaluated (wvalues=()) and others were not. An all-invalid pool became a (n, 0) array; moocore.pareto_rank assigned rank 0, so unevaluated individuals occupied the first front. sel_count < 0 only tested == 0, so sort_non_dominated(pop, -1) returned the first front instead of []. Hall of fame and MAP-Elites already skip missing, invalid, and non-finite fitness.

Fix. Ignore an individual whose fitness is missing, invalid, or non-finite. An empty rankable pool with a positive sel_count returns [[]], matching an empty input. sel_count <= 0 returns []. sort_constraint_dominated uses the same count guard.

Validators.

  • tests/test_various/test_sort_non_dominated.py::test_mixed_and_invalid_fitness_are_not_ranked
  • tests/test_various/test_sort_non_dominated.py::test_non_positive_sel_count_returns_empty

nsga_diversity crashed on an empty front

ordered[0] raised IndexError when population was empty. A single point and a collapsed front already return Deb's \(\Delta = 1\).

Fix. Return 1.0 when the front is empty.

Validator. tests/test_various/test_nsga_metrics.py::test_nsga_diversity_empty_front_is_one


SortingNetwork.draw crashed on empty and one-level networks

A network with no comparators has depth == 0. draw() built each wire as ["-"] * 7 * depth, which is an empty list, then wrote the wire index and " o" into cells 0 and 1. SortingNetwork(4) is a valid object; draw() raised IndexError instead of showing the wires.

A one-level network (depth == 1) still crashed after that floor. Spacers write at (index + 1) * 6 + 1; on the only level that is column 7, past a 7-wide grid.

Fix. Size the ASCII grid to max(7 * max(depth, 1), 6 * depth + 2) so labels fit when empty and the last-level spacer fits when depth == 1. Comparator columns stay at (index + 1) * 6.

Validators.

  • tests/test_various/test_sorting_network.py::test_sorting_network_draw_empty_network_labels_wires
  • tests/test_various/test_sorting_network.py::test_sorting_network_draw_single_level_includes_comparators

nsga_convergence and inv_gen_dist crashed on empty sets

scipy.spatial.distance.cdist requires 2-D arrays. An empty front or empty reference became a 1-D empty array and raised ValueError. Empty nsga_diversity already returns Deb's \(\Delta = 1\).

Fix. Return 0.0 when either point set is empty.

Validator. tests/test_various/test_nsga_metrics.py::test_nsga_convergence_and_igd_empty_sets_are_zero