File: constrained_moarchive.py

package info (click to toggle)
python-moarchiving 1.0.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 404 kB
  • sloc: python: 2,989; makefile: 6
file content (277 lines) | stat: -rw-r--r-- 12,059 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# -*- coding: utf-8 -*-
""" Module for the CMOArchive class, which provides additional functionality for constrained
multi-objective optimization to the MOArchive classes, while keeping the same interface. """

from moarchiving.moarchiving import BiobjectiveNondominatedSortedList as MOArchive2obj
from moarchiving.moarchiving3obj import MOArchive3obj
from moarchiving.moarchiving4obj import MOArchive4obj

import warnings as _warnings
try:
    import fractions
except ImportError:
    _warnings.warn('`fractions` module not installed, arbitrary precision hypervolume computation not available')

inf = float('inf')


class CMOArchive:
    """ Class CMOArchive provides additional functionality for constrained multi-objective
    optimization to the MOArchive classes, while keeping the same interface. """
    try:
        hypervolume_final_float_type = fractions.Fraction
        hypervolume_computation_float_type = fractions.Fraction
    except:
        hypervolume_final_float_type = float
        hypervolume_computation_float_type = float

    def __init__(self, list_of_f_vals=None, list_of_g_vals=None, reference_point=None,
                 infos=None, n_obj=None, tau=1, hypervolume_final_float_type=None,
                 hypervolume_computation_float_type=None):
        """ Initialize a CMOArchive object.

        Additionally to the list of objective values `list_of_f_vals`, also list of constraint
        vectors `list_of_g_vals` should be provided.
        The reference point is used for the hypervolume computation and pruning of the archive.
        The list of additional information `infos` can be used to store additional information
        for each objective vector.
        Tau is a threshold that is used for computing the indicator.
        """
        hypervolume_final_float_type = CMOArchive.hypervolume_final_float_type \
            if hypervolume_final_float_type is None else hypervolume_final_float_type
        hypervolume_computation_float_type = CMOArchive.hypervolume_computation_float_type \
            if hypervolume_computation_float_type is None else hypervolume_computation_float_type

        if n_obj == 2:
            self.archive = MOArchive2obj(
                reference_point=reference_point,
                hypervolume_final_float_type=hypervolume_final_float_type,
                hypervolume_computation_float_type=hypervolume_computation_float_type)
        elif n_obj == 3:
            self.archive = MOArchive3obj(
                reference_point=reference_point,
                hypervolume_final_float_type=hypervolume_final_float_type,
                hypervolume_computation_float_type=hypervolume_computation_float_type)
        elif n_obj == 4:
            self.archive = MOArchive4obj(
                reference_point=reference_point,
                hypervolume_final_float_type=hypervolume_final_float_type,
                hypervolume_computation_float_type=hypervolume_computation_float_type)

        self.tau = tau
        self.n_obj = n_obj
        self._hypervolume_plus_constr = -inf

        if list_of_f_vals is not None:
            self.add_list(list_of_f_vals, list_of_g_vals, infos)

    def __iter__(self):
        """ Return an iterator over the objective vectors in the archive. """
        return iter(self.archive)

    def __len__(self):
        """ Return the number of objective vectors in the archive. """
        return len(self.archive)

    def add(self, f_vals, g_vals, info=None):
        """ Add the objective vector f_vals with corresponding constraints to the archive
        if it is feasible. If no feasible solution was found yet, also update the indicator.

        >>> from moarchiving.get_archive import get_cmo_archive
        >>> moa = get_cmo_archive(reference_point=[5, 5], tau=10)
        >>> moa.add([4, 4], 0)
        >>> list(moa)
        [[4, 4]]
        >>> moa.add([3, 4], 1)
        >>> list(moa)
        [[4, 4]]
        >>> moa.add([2, 2], 0)
        >>> list(moa)
        [[2, 2]]
        """
        try:
            constraint_violation = sum([max(0, g) for g in g_vals])
        except TypeError:
            constraint_violation = max(g_vals, 0)

        if constraint_violation > 0:
            if (self.archive.reference_point is not None and
                    constraint_violation + self.tau < -self._hypervolume_plus_constr):
                self._hypervolume_plus_constr = -(constraint_violation + self.tau)
        else:
            self.archive.add(f_vals, info)

            if self.archive.reference_point is not None:
                self._hypervolume_plus_constr = max(self.archive._hypervolume_plus, -self.tau)

    def add_list(self, list_of_f_vals, list_of_g_vals, infos=None):
        """ Add a list of objective vectors f_vals with corresponding constraints vectors g_vals
        and infos to the archive.

        >>> from moarchiving.get_archive import get_cmo_archive
        >>> moa = get_cmo_archive(reference_point=[5, 5], tau=10)
        >>> moa.add_list([[4, 4], [3, 3], [2, 2]], [0, 1, 0])
        >>> list(moa)
        [[2, 2]]
        >>> moa.add_list([[1, 6], [1, 3], [3, 0]], [[0], [0], [10]])
        >>> list(moa)
        [[1, 3], [2, 2]]
         """
        if infos is None:
            infos = [None] * len(list_of_f_vals)

        if self._hypervolume_plus_constr < 0:
            for obj, cons, info in zip(list_of_f_vals, list_of_g_vals, infos):
                self.add(obj, cons, info)
        else:
            try:
                list_of_g_vals = [sum([max(g, 0) for g in g_vals]) for g_vals in list_of_g_vals]
            except TypeError:
                list_of_g_vals = [max(g_vals, 0) for g_vals in list_of_g_vals]

            list_of_f_vals = [f_vals for f_vals, g_vals in zip(list_of_f_vals, list_of_g_vals) if g_vals == 0]
            infos = [info for info, g_vals in zip(infos, list_of_g_vals) if g_vals == 0]

            self.archive.add_list(list(list_of_f_vals), list(infos))
            self._hypervolume_plus_constr = self.archive._hypervolume_plus

    def remove(self, f_vals):
        """ Remove a feasible point with objective vector f_vals from the archive.

        >>> from moarchiving.get_archive import get_cmo_archive
        >>> moa = get_cmo_archive([[2, 3], [1, 4], [4, 1]], [0, 0, 0], reference_point=[5, 5])
        >>> list(moa)
        [[1, 4], [2, 3], [4, 1]]
        >>> moa.remove([2, 3])
        >>> list(moa)
        [[1, 4], [4, 1]]
        """
        info = self.archive.remove(f_vals)
        self._hypervolume_plus_constr = self.archive._hypervolume_plus
        return info

    @property
    def hypervolume(self):
        """ Return the hypervolume indicator. """
        return self.archive.hypervolume

    @property
    def hypervolume_plus(self):
        """ Return the hypervolume_plus indicator. """
        return self.archive.hypervolume_plus

    @property
    def hypervolume_plus_constr(self):
        """ Return the hypervolume_plus_constr (icmop) indicator. """
        if self.archive.reference_point is None:
            raise ValueError("to compute the hypervolume_plus_constr indicator a reference"
                             " point is needed (must be given initially)")
        return self._hypervolume_plus_constr

    @property
    def contributing_hypervolumes(self):
        """ Return the hypervolume contributions of each point in the archive. """
        return self.archive.contributing_hypervolumes

    @property
    def infos(self):
        """ Return the list of additional information for each point in the archive. """
        return self.archive.infos

    def compute_hypervolume(self, reference_point=None):
        """ Compute the hypervolume of the archive. """
        if self.n_obj == 2:
            return self.archive.compute_hypervolume(reference_point)
        return self.archive.compute_hypervolume()

    def contributing_hypervolume(self, f_vals):
        """ Compute the hypervolume contribution of the objective vector f_vals to the archive. """
        return self.archive.contributing_hypervolume(f_vals)

    def copy(self):
        """ Return a deep copy of the CMOArchive object. """
        new_cmoa = CMOArchive(reference_point=self.archive.reference_point, tau=self.tau)
        new_cmoa.archive = self.archive.copy()
        new_cmoa._hypervolume_plus_constr = self._hypervolume_plus_constr
        return new_cmoa

    def distance_to_hypervolume_area(self, f_vals):
        """ Compute the distance of the objective vector f_vals to the hypervolume area. """
        return self.archive.distance_to_hypervolume_area(f_vals)

    def distance_to_pareto_front(self, f_vals, ref_factor=1):
        """ Compute the distance of the objective vector f_vals to the Pareto front. """
        return self.archive.distance_to_pareto_front(f_vals, ref_factor=ref_factor)

    def dominates(self, f_vals):
        """ Returns True if the objective vector f_vals is dominated by any of the
        points in the archive. """
        return self.archive.dominates(f_vals)

    def dominators(self, f_vals, number_only=False):
        """ Returns a list of points in the archive that dominate the objective vector f_vals.
        If number_only is True, only the number of dominators is returned. """
        return self.archive.dominators(f_vals, number_only=number_only)

    def hypervolume_improvement(self, f_vals):
        """ Compute the hypervolume improvement of the archive
        if the objective vector f_vals is added. """
        return self.archive.hypervolume_improvement(f_vals)

    def hypervolume_plus_constr_improvement(self, f_vals, g_vals):
        """ Compute the improvement of the indicator if the objective vector f_vals is added.

        >>> from moarchiving.get_archive import get_cmo_archive
        >>> get_cmo_archive.hypervolume_final_float_type = float
        >>> moa = get_cmo_archive(reference_point=[5, 5], tau=4) # hv+c = -inf
        >>> moa.hypervolume_plus_constr_improvement([1, 1], 10)
        inf
        >>> moa.add([1, 1], [10, 0]) # hv+c = -14
        >>> int(moa.hypervolume_plus_constr_improvement([2, 2], 4))
        6
        >>> moa.add([2, 2], [3, 1]) # hv+c = -8
        >>> int(moa.hypervolume_plus_constr_improvement([8, 9], 0))
        4
        >>> moa.add([8, 9], [0, 0]) # hv+c = -4
        >>> int(moa.hypervolume_plus_constr_improvement([8, 5], 0))
        1
        >>> moa.add([8, 5], [0, 0]) # hv+c = -3
        >>> int(moa.hypervolume_plus_constr_improvement([0, 0], 1))
        0
        >>> moa.add([0, 0], [1, -3]) # hv+c = -3
        >>> int(moa.hypervolume_plus_constr_improvement([4, 4], 0))
        4
        >>> moa.add([4, 4], [0, 0]) # hv+c = 1
        >>> int(moa.hypervolume_plus_constr_improvement([3, 3], 0))
        3
        """
        try:
            constraint_violation = sum([max(0, g) for g in g_vals])
        except TypeError:
            constraint_violation = max(g_vals, 0)
        if constraint_violation > 0:
            if constraint_violation + self.tau < -self._hypervolume_plus_constr:
                return - self._hypervolume_plus_constr - (constraint_violation + self.tau)
            return 0

        if not self.in_domain(f_vals):
            if self._hypervolume_plus_constr > 0:
                return 0
            distance_to_hv_area = min(self.distance_to_hypervolume_area(f_vals), self.tau)
            if distance_to_hv_area < - self._hypervolume_plus_constr:
                return -self._hypervolume_plus_constr - distance_to_hv_area
            return 0

        if not self.dominates(f_vals):
            return max(-self._hypervolume_plus_constr, 0) + self.hypervolume_improvement(f_vals)
        return 0

    def in_domain(self, f_vals, reference_point=None):
        """ Returns True if the objective vector f_vals dominates the reference point. """
        return self.archive.in_domain(f_vals, reference_point=reference_point)


if __name__ == "__main__":
    import doctest
    print('doctest.testmod() in constrained_moarchive.py')
    print(doctest.testmod())