nmoo.utils.population

Population related methods.

  1"""
  2Population related methods.
  3"""
  4__docformat__ = "google"
  5
  6from typing import Dict, List, Union
  7
  8import numpy as np
  9from pymoo.core.population import Population
 10
 11
 12def pareto_frontier_mask(arr: np.ndarray) -> np.ndarray:
 13    """
 14    Computes the Pareto frontier of a set of points. The array must have an
 15    `ndim` of 2.
 16
 17    Returns:
 18        A mask (array of booleans) on `arr` selecting the points belonging to
 19        the Pareto frontier. The actual Pareto frontier can be computed with
 20
 21            pfm = pareto_frontier_mask(arr)
 22            pf = arr[pfm]
 23
 24    Warning:
 25        The direction of the optimum is assumed to towards the "all negative"
 26        quadrant, i.e. $a$ (weakly) dominates $b$ if $a_i \\leq b_i$ for all $0
 27        \\leq i < d$.
 28
 29    Todo:
 30        * Possibility to specify a direction of optimum;
 31        * fancy tree-based optimization.
 32    """
 33    if not arr.ndim == 2:
 34        raise ValueError("The input array must be of shape (N, d).")
 35
 36    d = arr.shape[-1]
 37
 38    if d == 1:
 39        mask = np.full(len(arr), False)
 40        mask[arr.argmin()] = True
 41        return mask
 42    if d == 2:
 43        return pareto_frontier_mask_2d(arr)
 44
 45    argsort0, mask = np.argsort(arr[:, 0]), np.full(len(arr), True)
 46    for i, j in enumerate(argsort0):
 47        if not mask[j]:
 48            continue
 49        for k in filter(lambda x: mask[x], argsort0[i + 1 :]):
 50            # faster than (arr[k] <= arr[j]).any()
 51            for l in range(1, d):
 52                if arr[k][l] <= arr[j][l]:
 53                    mask[k] = True
 54                    break
 55            else:
 56                mask[k] = False
 57    return mask
 58
 59
 60def pareto_frontier_mask_2d(arr: np.ndarray) -> np.ndarray:
 61    """
 62    Computes the Pareto frontier of a set of 2D points. Faster than
 63    `pareto_frontier_mask`. Note that `pareto_frontier_mask` will automatically
 64    call this method whenever possible.
 65
 66    Returns:
 67        A mask (array of booleans) on `arr` selecting the points belonging to
 68        the Pareto frontier. The actual Pareto frontier can be computed with
 69
 70            pfm = pareto_frontier_mask(arr)
 71            pf = arr[pfm]
 72
 73    Warning:
 74        The direction of the optimum is assumed to be south-west.
 75    """
 76    if not (arr.ndim == 2 and arr.shape[-1] == 2):
 77        raise ValueError("The input array must be of shape (N, 2).")
 78    argsort0, mask = np.argsort(arr[:, 0]), np.full(len(arr), False)
 79    i = argsort0[0]  # Index of the last Pareto point
 80    mask[i] = True
 81    for j in argsort0:
 82        # Current point is in the south-east quadrant of the last Pareto point
 83        mask[j] = arr[j][1] <= arr[i][1]
 84        if mask[j]:
 85            i = j
 86    return mask
 87
 88
 89def population_list_to_dict(
 90    populations: Union[Population, List[Population]]
 91) -> Dict[str, np.ndarray]:
 92    """
 93    Transforms a list of pymoo Population (or a single Population) into a dict
 94    containing the following
 95
 96    * `X`: an `np.array` containing all `X` fields of all individuals across
 97      all populations;
 98    * `F`: an `np.array` containing all `F` fields of all individuals across
 99      all populations;
100    * `G`: an `np.array` containing all `G` fields of all individuals across
101      all populations;
102    * `dF`: an `np.array` containing all `dF` fields of all individuals across
103      all populations;
104    * `dG`: an `np.array` containing all `dG` fields of all individuals across
105      all populations;
106    * `ddF`: an `np.array` containing all `ddF` fields of all individuals
107      across all populations;
108    * `ddG`: an `np.array` containing all `ddG` fields of all individuals
109      across all populations;
110    * `CV`: an `np.array` containing all `CV` fields of all individuals across
111      all populations;
112    * `feasible`: an `np.array` containing all `feasible` fields of all
113      individuals across all populations;
114    * `_batch`: the index of the population the individual belongs to.
115
116    So all `np.arrays` have the same length, which is the total number of
117    individual across all populations. Each "row" corresponds to the data
118    associated to this individual (`X`, `F`, `G`, `dF`, `dG`, `ddF`, `ddG`,
119    `CV`, `feasible`), as well as the population index it belongs to
120    (`_batch`).
121    """
122    if isinstance(populations, Population):
123        populations = [populations]
124    fields = ["X", "F", "G", "dF", "dG", "ddF", "ddG", "CV", "feasible"]
125    data: Dict[str, List[np.ndarray]] = {f: [] for f in fields + ["_batch"]}
126    if not populations:
127        return {k: np.array([]) for k in data}
128    for i, pop in enumerate(populations):
129        for f in fields:
130            data[f].append(pop.get(f))
131        data["_batch"].append(np.full(len(pop), i + 1))
132    return {k: np.concatenate(v) for k, v in data.items()}
def pareto_frontier_mask(arr: numpy.ndarray) -> numpy.ndarray:
13def pareto_frontier_mask(arr: np.ndarray) -> np.ndarray:
14    """
15    Computes the Pareto frontier of a set of points. The array must have an
16    `ndim` of 2.
17
18    Returns:
19        A mask (array of booleans) on `arr` selecting the points belonging to
20        the Pareto frontier. The actual Pareto frontier can be computed with
21
22            pfm = pareto_frontier_mask(arr)
23            pf = arr[pfm]
24
25    Warning:
26        The direction of the optimum is assumed to towards the "all negative"
27        quadrant, i.e. $a$ (weakly) dominates $b$ if $a_i \\leq b_i$ for all $0
28        \\leq i < d$.
29
30    Todo:
31        * Possibility to specify a direction of optimum;
32        * fancy tree-based optimization.
33    """
34    if not arr.ndim == 2:
35        raise ValueError("The input array must be of shape (N, d).")
36
37    d = arr.shape[-1]
38
39    if d == 1:
40        mask = np.full(len(arr), False)
41        mask[arr.argmin()] = True
42        return mask
43    if d == 2:
44        return pareto_frontier_mask_2d(arr)
45
46    argsort0, mask = np.argsort(arr[:, 0]), np.full(len(arr), True)
47    for i, j in enumerate(argsort0):
48        if not mask[j]:
49            continue
50        for k in filter(lambda x: mask[x], argsort0[i + 1 :]):
51            # faster than (arr[k] <= arr[j]).any()
52            for l in range(1, d):
53                if arr[k][l] <= arr[j][l]:
54                    mask[k] = True
55                    break
56            else:
57                mask[k] = False
58    return mask

Computes the Pareto frontier of a set of points. The array must have an ndim of 2.

Returns:

A mask (array of booleans) on arr selecting the points belonging to the Pareto frontier. The actual Pareto frontier can be computed with

pfm = pareto_frontier_mask(arr)
pf = arr[pfm]
Warning:

The direction of the optimum is assumed to towards the "all negative" quadrant, i.e. $a$ (weakly) dominates $b$ if $a_i \leq b_i$ for all $0 \leq i < d$.

Todo:
  • Possibility to specify a direction of optimum;
  • fancy tree-based optimization.
def pareto_frontier_mask_2d(arr: numpy.ndarray) -> numpy.ndarray:
61def pareto_frontier_mask_2d(arr: np.ndarray) -> np.ndarray:
62    """
63    Computes the Pareto frontier of a set of 2D points. Faster than
64    `pareto_frontier_mask`. Note that `pareto_frontier_mask` will automatically
65    call this method whenever possible.
66
67    Returns:
68        A mask (array of booleans) on `arr` selecting the points belonging to
69        the Pareto frontier. The actual Pareto frontier can be computed with
70
71            pfm = pareto_frontier_mask(arr)
72            pf = arr[pfm]
73
74    Warning:
75        The direction of the optimum is assumed to be south-west.
76    """
77    if not (arr.ndim == 2 and arr.shape[-1] == 2):
78        raise ValueError("The input array must be of shape (N, 2).")
79    argsort0, mask = np.argsort(arr[:, 0]), np.full(len(arr), False)
80    i = argsort0[0]  # Index of the last Pareto point
81    mask[i] = True
82    for j in argsort0:
83        # Current point is in the south-east quadrant of the last Pareto point
84        mask[j] = arr[j][1] <= arr[i][1]
85        if mask[j]:
86            i = j
87    return mask

Computes the Pareto frontier of a set of 2D points. Faster than pareto_frontier_mask. Note that pareto_frontier_mask will automatically call this method whenever possible.

Returns:

A mask (array of booleans) on arr selecting the points belonging to the Pareto frontier. The actual Pareto frontier can be computed with

pfm = pareto_frontier_mask(arr)
pf = arr[pfm]
Warning:

The direction of the optimum is assumed to be south-west.

def population_list_to_dict( populations: Union[pymoo.core.population.Population, List[pymoo.core.population.Population]]) -> Dict[str, numpy.ndarray]:
 90def population_list_to_dict(
 91    populations: Union[Population, List[Population]]
 92) -> Dict[str, np.ndarray]:
 93    """
 94    Transforms a list of pymoo Population (or a single Population) into a dict
 95    containing the following
 96
 97    * `X`: an `np.array` containing all `X` fields of all individuals across
 98      all populations;
 99    * `F`: an `np.array` containing all `F` fields of all individuals across
100      all populations;
101    * `G`: an `np.array` containing all `G` fields of all individuals across
102      all populations;
103    * `dF`: an `np.array` containing all `dF` fields of all individuals across
104      all populations;
105    * `dG`: an `np.array` containing all `dG` fields of all individuals across
106      all populations;
107    * `ddF`: an `np.array` containing all `ddF` fields of all individuals
108      across all populations;
109    * `ddG`: an `np.array` containing all `ddG` fields of all individuals
110      across all populations;
111    * `CV`: an `np.array` containing all `CV` fields of all individuals across
112      all populations;
113    * `feasible`: an `np.array` containing all `feasible` fields of all
114      individuals across all populations;
115    * `_batch`: the index of the population the individual belongs to.
116
117    So all `np.arrays` have the same length, which is the total number of
118    individual across all populations. Each "row" corresponds to the data
119    associated to this individual (`X`, `F`, `G`, `dF`, `dG`, `ddF`, `ddG`,
120    `CV`, `feasible`), as well as the population index it belongs to
121    (`_batch`).
122    """
123    if isinstance(populations, Population):
124        populations = [populations]
125    fields = ["X", "F", "G", "dF", "dG", "ddF", "ddG", "CV", "feasible"]
126    data: Dict[str, List[np.ndarray]] = {f: [] for f in fields + ["_batch"]}
127    if not populations:
128        return {k: np.array([]) for k in data}
129    for i, pop in enumerate(populations):
130        for f in fields:
131            data[f].append(pop.get(f))
132        data["_batch"].append(np.full(len(pop), i + 1))
133    return {k: np.concatenate(v) for k, v in data.items()}

Transforms a list of pymoo Population (or a single Population) into a dict containing the following

  • X: an np.array containing all X fields of all individuals across all populations;
  • F: an np.array containing all F fields of all individuals across all populations;
  • G: an np.array containing all G fields of all individuals across all populations;
  • dF: an np.array containing all dF fields of all individuals across all populations;
  • dG: an np.array containing all dG fields of all individuals across all populations;
  • ddF: an np.array containing all ddF fields of all individuals across all populations;
  • ddG: an np.array containing all ddG fields of all individuals across all populations;
  • CV: an np.array containing all CV fields of all individuals across all populations;
  • feasible: an np.array containing all feasible fields of all individuals across all populations;
  • _batch: the index of the population the individual belongs to.

So all np.arrays have the same length, which is the total number of individual across all populations. Each "row" corresponds to the data associated to this individual (X, F, G, dF, dG, ddF, ddG, CV, feasible), as well as the population index it belongs to (_batch).