From 28fa1f93ec893f7609d110f51646b797e7298c0e Mon Sep 17 00:00:00 2001 From: "tomtkg, Ph.D" <39430259+tomtkg@users.noreply.github.com> Date: Fri, 19 Jan 2024 13:04:23 +0900 Subject: [PATCH] Add Incremental method (#536) * Add reference for incremental method. * Add Incremental method to reference_directions.ipynb * Create incremental.py * Add IncrementalReferenceDirectionFactory * Update test_reference_directions.py * Update test_reference_directions.py --- docs/source/misc/reference_directions.ipynb | 26 ++++++++ docs/source/references.bib | 17 ++++++ pymoo/util/ref_dirs/__init__.py | 2 + pymoo/util/ref_dirs/incremental.py | 68 +++++++++++++++++++++ tests/misc/test_reference_directions.py | 30 +++++++++ 5 files changed, 143 insertions(+) create mode 100644 pymoo/util/ref_dirs/incremental.py diff --git a/docs/source/misc/reference_directions.ipynb b/docs/source/misc/reference_directions.ipynb index 337a10391..049d5f184 100644 --- a/docs/source/misc/reference_directions.ipynb +++ b/docs/source/misc/reference_directions.ipynb @@ -357,6 +357,32 @@ "ref_dirs = get_reference_directions(\"layer-energy\", 3, [9, 5, 2, 1])\n", "Scatter().add(ref_dirs).show()" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Incremental method" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Incremental method can be used to create reference directions in increments. \n", + "However, the method relies on a partition number `n_partitions` which determines how many points will be sampled.\n", + "Points can be uniformly create on the unit hyperplane using the Incremental method proposed in ." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ref_dirs = get_reference_directions(\"incremental\", 3, n_partitions=8)\n", + "Scatter().add(ref_dirs).show()" + ] } ], "metadata": { diff --git a/docs/source/references.bib b/docs/source/references.bib index 88d76ed3b..30fd7d888 100644 --- a/docs/source/references.bib +++ b/docs/source/references.bib @@ -252,6 +252,23 @@ @article{das_dennis keywords = {Pareto set, multicriteria optimization, multiobjective optimization, trade-off curve}, } +@inproceedings{incremental_lattice, + author = {Takagi, Tomoaki and Takadama, Keiki and Sato, Hiroyuki}, + title = {Incremental Lattice Design of Weight Vector Set}, + booktitle = {Proceedings of the 2020 Genetic and Evolutionary Computation Conference Companion}, + series = {GECCO '20}, + year = {2020}, + isbn = {9781450371278}, + location = {Canc\'{u}n, Mexico}, + pages = {1486--1494}, + numpages = {9}, + url = {https://doi.org/10.1145/3377929.3398082}, + doi = {10.1145/3377929.3398082}, + publisher = {Association for Computing Machinery}, + address = {New York, NY, USA}, + keywords = {uniform mixture design, weight vector set, evolutionary algorithm, multi-objective optimization, many-objective optimization}, +} + @book{multi_objective_book, author = {Kalyanmoy, Deb}, title = {Multi-Objective Optimization Using Evolutionary Algorithms}, diff --git a/pymoo/util/ref_dirs/__init__.py b/pymoo/util/ref_dirs/__init__.py index 5ef7b2d52..37cf6a425 100644 --- a/pymoo/util/ref_dirs/__init__.py +++ b/pymoo/util/ref_dirs/__init__.py @@ -1,6 +1,7 @@ from pymoo.util.ref_dirs.energy import RieszEnergyReferenceDirectionFactory from pymoo.util.ref_dirs.energy_layer import LayerwiseRieszEnergyReferenceDirectionFactory from pymoo.util.ref_dirs.reduction import ReductionBasedReferenceDirectionFactory +from pymoo.util.ref_dirs.incremental import IncrementalReferenceDirectionFactory from pymoo.util.reference_direction import MultiLayerReferenceDirectionFactory @@ -14,6 +15,7 @@ def get_reference_directions(name, *args, **kwargs): "multi-layer": MultiLayerReferenceDirectionFactory, "layer-energy": LayerwiseRieszEnergyReferenceDirectionFactory, "reduction": ReductionBasedReferenceDirectionFactory, + "incremental": IncrementalReferenceDirectionFactory, } if name not in REF: diff --git a/pymoo/util/ref_dirs/incremental.py b/pymoo/util/ref_dirs/incremental.py new file mode 100644 index 000000000..322ebe08c --- /dev/null +++ b/pymoo/util/ref_dirs/incremental.py @@ -0,0 +1,68 @@ +import numpy as np + +from pymoo.util.reference_direction import ReferenceDirectionFactory + +def check_n_points(n_points, n_dim): + """ + Returns n_partitions or a numeric value associated with the exception message. + """ + + if n_dim == 1: + return [0] + + I = n_dim * np.eye(n_dim) + W = np.zeros((1, n_dim)) + edgeW = W + i = 0 + + while len(W) < n_points: + edgeW = np.tile(edgeW, (n_dim, 1)) + np.repeat(I, edgeW.shape[0], axis=0) + edgeW = np.unique(edgeW, axis=0) + edgeW = edgeW [np.any(edgeW == 0, axis=1)] + W = np.vstack((W + 1, edgeW)) + i += 1 + + if len(W) == n_points: + return [i] + + return [len(W) - len(edgeW), i - 1, len(W), i] + + +def incremental_lattice(n_partitions, n_dim): + I = n_dim * np.eye(n_dim) + W = np.zeros((1, n_dim)) + edgeW = W + + for _ in range(n_partitions): + edgeW = np.tile(edgeW, (n_dim, 1)) + np.repeat(I, edgeW.shape[0], axis=0) + edgeW = np.unique(edgeW, axis=0) + edgeW = edgeW [np.any(edgeW == 0, axis=1)] + W = np.vstack((W + 1, edgeW)) + + return W / (n_dim * n_partitions) + +class IncrementalReferenceDirectionFactory(ReferenceDirectionFactory): + + def __init__(self, n_dim, scaling=None, n_points=None, n_partitions=None, **kwargs) -> None: + super().__init__(n_dim, scaling=scaling, **kwargs) + + if n_points is not None: + results = check_n_points(n_points, n_dim) + + # the number of points are not matching to any partition number + if len(results) > 1: + raise Exception("The number of points (n_points = %s) can not be created uniformly.\n" + "Either choose n_points = %s (n_partitions = %s) or " + "n_points = %s (n_partitions = %s)." % + (n_points, results[0], results[1], results[2], results[3])) + + self.n_partitions = results[0] + + elif n_partitions is not None: + self.n_partitions = n_partitions + + else: + raise Exception("Either provide number of partitions or number of points.") + + def _do(self): + return incremental_lattice(self.n_partitions, self.n_dim) diff --git a/tests/misc/test_reference_directions.py b/tests/misc/test_reference_directions.py index ef7064d90..01cf16f2c 100644 --- a/tests/misc/test_reference_directions.py +++ b/tests/misc/test_reference_directions.py @@ -24,6 +24,36 @@ def test_das_dennis_not_achievable_points(): get_reference_directions("das-dennis", 3, n_points=92) +def test_incremental(): + ref_dirs = get_reference_directions("incremental", 3, n_partitions=8) + assert len(ref_dirs) == 109 + +def test_incremental_achievable_points(): + ref_dirs = get_reference_directions("incremental", 3, n_points=109) + assert len(ref_dirs) == 109 + +def test_incremental2(): + N = [[], [], + [1, 3, 5, 7, 9, 11, 13, 15, 17, 19], + [1, 4, 10, 19, 31, 46, 64, 85, 109, 136], + [1, 5, 15, 35, 69, 121, 195, 295, 425, 589], + [1, 6, 21, 56, 126, 251, 456, 771, 1231, 1876], + [1, 7, 28, 84, 210, 462, 923, 1709, 2975, 4921], + [1, 8, 36, 120, 330, 792, 1716, 3431, 6427, 11404], + [1, 9, 45, 165, 495, 1287, 3003, 6435, 12869, 24301]] + for i, list in enumerate(N): + for j, x in enumerate(list): + ref_dirs = get_reference_directions("incremental", i, n_partitions=j) + assert len(ref_dirs) == x + ref_dirs = get_reference_directions("incremental", i, n_points=x) + assert len(ref_dirs) == x + + +@pytest.mark.xfail(raises=Exception) +def test_incremental_not_achievable_points(): + get_reference_directions("incremental", 3, n_points=110) + + def test_unit_simplex_sampling(): n_points = 1000 n_dim = 3