saealib.utils.weight_vectors のソースコード
"""Uniform weight-vector generation for multi-objective decomposition."""
from __future__ import annotations
import math
import numpy as np
[ドキュメント]
def uniform_weight_vectors(n_obj: int, n_divisions: int) -> np.ndarray:
"""
Generate weight vectors uniformly distributed on the unit simplex.
Uses the simplex lattice design described in Das & Dennis (1998), widely
applied in MOEA/D (Zhang & Li, 2007) and NSGA-III (Deb & Jain, 2014).
Each weight vector ``w`` satisfies ``sum(w) == 1`` and ``w_i >= 0``.
The granularity is ``1 / n_divisions``; each component takes a value in
``{0, 1/H, 2/H, ..., 1}`` where ``H = n_divisions``.
The number of vectors returned is ``math.comb(n_obj + H - 1, H)``.
Parameters
----------
n_obj : int
Number of objectives. Must be >= 2.
n_divisions : int
Number of divisions ``H`` along each axis. Must be >= 1. Larger values
produce more weight vectors and finer coverage.
Returns
-------
np.ndarray
Weight matrix, shape ``(N, n_obj)``. Each row sums to 1.0 and all
entries are non-negative.
Raises
------
ValueError
If ``n_obj < 2`` or ``n_divisions < 1``.
References
----------
:cite:`zhang2007moead`: Zhang, Q., & Li, H. (2007). MOEA/D: A
multiobjective evolutionary algorithm based on decomposition. *IEEE
Transactions on Evolutionary Computation*, 11(6), 712-731. (Appendix A
describes the simplex lattice design originally due to Das & Dennis,
1998.)
Examples
--------
>>> uniform_weight_vectors(2, 4)
array([[0. , 1. ],
[0.25, 0.75],
[0.5 , 0.5 ],
[0.75, 0.25],
[1. , 0. ]])
"""
if n_obj < 2:
raise ValueError(f"n_obj must be >= 2; got {n_obj}")
if n_divisions < 1:
raise ValueError(f"n_divisions must be >= 1; got {n_divisions}")
n_vectors = math.comb(n_obj + n_divisions - 1, n_divisions)
rows: list[list[int]] = []
_fill(n_obj, n_divisions, n_divisions, [], rows)
assert len(rows) == n_vectors # sanity check
return np.array(rows, dtype=float) / n_divisions
def _fill(
n_obj: int,
remaining: int,
n_divisions: int,
current: list[int],
rows: list[list[int]],
) -> None:
"""Enumerate integer partitions of *n_divisions* into *n_obj* parts."""
if len(current) == n_obj - 1:
rows.append([*current, remaining])
return
for k in range(remaining + 1):
_fill(n_obj, remaining - k, n_divisions, [*current, k], rows)