Skip to content

Conversation

@richardcsuwandi
Copy link
Contributor

This PR introduces a faster non-dominated sorting algorithm that leverages geometric properties to reduce complexity from O(N^2) to O(N log N), resulting in up to 60.92x faster performance for bi-objective problems.

Before (O(N^2) for all cases):

# Original algorithm for all objective counts
for i in range(n):
    for j in range(i + 1, n):
        rel = M[i, j]  # Full dominance matrix calculation
        if rel == 1:
            is_dominating[i].append(j)
            n_dominated[j] += 1
        elif rel == -1:
            is_dominating[j].append(i)
            n_dominated[i] += 1

After (O(N log N) for bi-objective, O(N^2) for others):

# Specialized algorithm for bi-objective problems
if n_objectives == 2:
    return _fast_biobjective_nondominated_sort(F)  # O(N log N) algorithm
else:
    # Fall back to optimized original approach for multi-objective

📊 Performance Results

Size Original (s) Evolved (s) Speedup
50 0.000328 0.000036 9.00x
100 0.001024 0.000078 13.18x
500 0.034089 0.000842 40.49x
1000 0.104040 0.002246 46.33x
2000 0.278000 0.004547 60.92x

Average speedup: 33.98x

Added comprehensive performance test (test_comparison.py) that:

  • Uses the same interface as the original pymoo implementation
  • Tests with actual bi-objective optimization scenarios
  • Verifies identical results between original and optimized methods
  • Demonstrates performance improvements across multiple problem sizes

This PR introduces a faster non-dominated sorting algorithm that leverages geometric properties to reduce complexity from O(N^2) to O(N log N), resulting in up to 60.92x faster performance for bi-objective problems.

  Before (O(N^2) for all cases):
  ```python
  # Original algorithm for all objective counts
  for i in range(n):
      for j in range(i + 1, n):
          rel = M[i, j]  # Full dominance matrix calculation
          if rel == 1:
              is_dominating[i].append(j)
              n_dominated[j] += 1
          elif rel == -1:
              is_dominating[j].append(i)
              n_dominated[i] += 1
  ```

  After (O(N log N) for bi-objective, O(N^2) for others):
  ```python
  # Specialized algorithm for bi-objective problems
  if n_objectives == 2:
      return _fast_biobjective_nondominated_sort(F)  # O(N log N) algorithm
  else:
      # Fall back to optimized original approach for multi-objective
  ```

📊 Performance Results

| Size | Original (s) | Evolved (s) | Speedup |
|------|--------------|-------------|---------|
| 50   | 0.000328     | 0.000036    | 9.00x   |
| 100  | 0.001024     | 0.000078    | 13.18x  |
| 500  | 0.034089     | 0.000842    | 40.49x  |
| 1000 | 0.104040     | 0.002246    | 46.33x  |
| 2000 | 0.278000     | 0.004547    | 60.92x  |
|------|--------------|-------------|---------|

Average speedup: 33.98x

Added comprehensive performance test (`test_comparison.py`) that:

- Uses the same interface as the original `pymoo` implementation
- Tests with actual bi-objective optimization scenarios
- Verifies identical results between original and optimized methods
- Demonstrates performance improvements across multiple problem sizes
@blankjul blankjul self-assigned this Oct 5, 2025
@blankjul
Copy link
Collaborator

blankjul commented Oct 5, 2025

Amazing thanks for your contribution!

I was under the impression that this already exists somewhere in the codebase, but doesn't seem like it!

@blankjul blankjul merged commit a799e5d into anyoptimization:main Oct 5, 2025
@MLopez-Ibanez
Copy link
Contributor

@richardcsuwandi @blankjul

How does this new implementation compare with https://multi-objective.github.io/moocore/python/reference/generated/moocore.pareto_rank.html#moocore.pareto_rank

which already uses the O(n log n) algorithm by Jensen?

Of course, you do whatever you want with pymoo but it does seem wasteful to re-implement functionality that is already provided by moocore which you already have as a dependency. That effort would be better spent on improving pymoo functionality not present in moocore or improving moocore itself.

For benchmarking, you could use the benchmarking code of moocore, see: https://github.com/multi-objective/moocore/blob/main/python/benchmarks/bench_ndom.py

@MLopez-Ibanez
Copy link
Contributor

Also, why would you want the file test_comparison.py in the root of the source directory and not under tests/? Is this a file that should be available to users of the package?

AnonymeMeow pushed a commit to AnonymeMeow/pymoo-backport that referenced this pull request Nov 4, 2025
…nyoptimization#754)

This PR introduces a faster non-dominated sorting algorithm that leverages geometric properties to reduce complexity from O(N^2) to O(N log N), resulting in up to 60.92x faster performance for bi-objective problems.

  Before (O(N^2) for all cases):
  ```python
  # Original algorithm for all objective counts
  for i in range(n):
      for j in range(i + 1, n):
          rel = M[i, j]  # Full dominance matrix calculation
          if rel == 1:
              is_dominating[i].append(j)
              n_dominated[j] += 1
          elif rel == -1:
              is_dominating[j].append(i)
              n_dominated[i] += 1
  ```

  After (O(N log N) for bi-objective, O(N^2) for others):
  ```python
  # Specialized algorithm for bi-objective problems
  if n_objectives == 2:
      return _fast_biobjective_nondominated_sort(F)  # O(N log N) algorithm
  else:
      # Fall back to optimized original approach for multi-objective
  ```

📊 Performance Results

| Size | Original (s) | Evolved (s) | Speedup |
|------|--------------|-------------|---------|
| 50   | 0.000328     | 0.000036    | 9.00x   |
| 100  | 0.001024     | 0.000078    | 13.18x  |
| 500  | 0.034089     | 0.000842    | 40.49x  |
| 1000 | 0.104040     | 0.002246    | 46.33x  |
| 2000 | 0.278000     | 0.004547    | 60.92x  |
|------|--------------|-------------|---------|

Average speedup: 33.98x

Added comprehensive performance test (`test_comparison.py`) that:

- Uses the same interface as the original `pymoo` implementation
- Tests with actual bi-objective optimization scenarios
- Verifies identical results between original and optimized methods
- Demonstrates performance improvements across multiple problem sizes
@blankjul
Copy link
Collaborator

all fair points. benchmark against the current implementation as well:

Bi-Objective Non-dominated Sorting Performance Benchmark
======================================================================
Size       Default (ms)    Native BiObj (ms)  Speedup    Correct
----------------------------------------------------------------------
50         0.107           0.036              2.98      x Yes
100        0.103           0.100              1.03      x Yes
500        1.038           1.065              0.98      x Yes
1000       3.205           3.094              1.04      x Yes
2000       8.615           8.642              1.00      x Yes
----------------------------------------------------------------------
All results correct: Yes

Decided not to make it default.

and thanks for the callout. decided to make a benchmark folder within tests.

blankjul added a commit that referenced this pull request Nov 24, 2025
…754

- Add native_biobj_sorting parameter to fast_non_dominated_sort (default False)
- Move test_comparison.py from pymoo/ to tests/benchmark/run_native_biobj.py
- Simplify benchmark to use actual pymoo implementation

Benchmark results show minimal performance improvement:

  Size       Default (ms)    Native BiObj (ms)  Speedup    Correct
  50         0.107           0.036              2.98x      Yes
  100        0.103           0.100              1.03x      Yes
  500        1.038           1.065              0.98x      Yes
  1000       3.205           3.094              1.04x      Yes
  2000       8.615           8.642              1.00x      Yes

Not much improvement compared to moocore, thus not enabled by default.

Addresses review comments from @MLopez-Ibanez
@MLopez-Ibanez
Copy link
Contributor

all fair points. benchmark against the current implementation as well:

I recently added moocore benchmarks for nondominated sorting: https://multi-objective.github.io/moocore/python/#nondominated-sorting-pareto-ranking

As you can see the current implementation of pymoo is the slowest of the ones tested.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants