88import copy
99import numpy as np
1010import time
11- from typing import Union
11+ from typing import Union , Tuple
1212from functools import reduce , partial
1313
14- from epde .optimizers .moeadd .moeadd import ParetoLevels
14+ from epde .optimizers .moeadd .moeadd import ParetoLevels , ObjFunNormalizer
1515from epde .operators .utils .template import CompoundOperator , add_base_param_to_operator
1616from epde .operators .multiobjective .mutations import get_basic_mutation
1717
18+ from epde .structure .main_structures import SoEq
1819
19- def penalty_based_intersection (sol_obj , weight , ideal_obj , penalty_factor = 1. ) -> float :
20+
21+ def penalty_based_intersection (sol_obj , weight , ideal_obj ,
22+ penalty_factor = 1. , obj_normalizer : ObjFunNormalizer = None ) -> float :
2023 '''
2124 Calculation of the penalty pased intersection, that is minimized for the solutions inside the
2225 domain, specified by **weight** vector. The calculations are held, according to the following formulas:
@@ -50,10 +53,17 @@ def penalty_based_intersection(sol_obj, weight, ideal_obj, penalty_factor = 1.)
5053
5154 penalty_factor : float, optional, default 1.
5255 The penalty parameter, represents :math:`\Theta` in the equations.
56+
57+ obj_normalizer : ObjFunNormalizer obj., optional, defaut None.
58+ Normalizer for solution objective functions.
5359
5460 '''
55- d_1 = np .dot ((sol_obj .obj_fun - ideal_obj ), weight ) / np .linalg .norm (weight )
56- d_2 = np .linalg .norm (sol_obj .obj_fun - (ideal_obj + d_1 * weight / np .linalg .norm (weight )))
61+ print (f'Objective before normalization: { sol_obj .obj_fun } for normalizer { obj_normalizer } ' )
62+ solution_objective = sol_obj .obj_fun if obj_normalizer is None else obj_normalizer (sol_obj .obj_fun )
63+ print (f'Objective after expected normalization: { solution_objective } ' )
64+
65+ d_1 = np .dot ((solution_objective - ideal_obj ), weight ) / np .linalg .norm (weight )
66+ d_2 = np .linalg .norm (solution_objective - (ideal_obj + d_1 * weight / np .linalg .norm (weight )))
5767 return d_1 + penalty_factor * d_2
5868
5969
@@ -87,7 +97,7 @@ def population_to_sectors(population, weights):
8797 return list (map (solution_selection , np .arange (len (weights ))))
8898
8999
90- def locate_pareto_worst (levels , weights , best_obj , penalty_factor = 1. ):
100+ def locate_pareto_worst (levels : ParetoLevels , weights : np . ndarray , best_obj : np . ndarray , penalty_factor : float = 1. ):
91101 '''
92102
93103 Function, dedicated to the selection of the worst solution on the Pareto levels.
@@ -114,7 +124,8 @@ def locate_pareto_worst(levels, weights, best_obj, penalty_factor = 1.):
114124 if len (crowded_domains ) == 1 :
115125 most_crowded_domain = crowded_domains [0 ]
116126 else :
117- PBI = lambda domain_idx : sum ([penalty_based_intersection (sol_obj , weights [domain_idx ], best_obj , penalty_factor ) for sol_obj in domain_solutions [domain_idx ]])
127+ PBI = lambda domain_idx : sum ([penalty_based_intersection (sol_obj , weights [domain_idx ], best_obj , penalty_factor , levels .normalizer )
128+ for sol_obj in domain_solutions [domain_idx ]])
118129 PBIS = np .fromiter (map (PBI , crowded_domains ), dtype = float )
119130 most_crowded_domain = crowded_domains [np .argmax (PBIS )]
120131
@@ -127,21 +138,34 @@ def locate_pareto_worst(levels, weights, best_obj, penalty_factor = 1.):
127138 max_level = np .max (domain_solution_NDL_idxs )
128139 worst_NDL_section = [domain_solutions [most_crowded_domain ][sol_idx ] for sol_idx in np .arange (len (domain_solutions [most_crowded_domain ]))
129140 if domain_solution_NDL_idxs [sol_idx ] == max_level ]
130- PBIS = np .fromiter (map (lambda solution : penalty_based_intersection (solution , weights [most_crowded_domain ], best_obj , penalty_factor ), worst_NDL_section ), dtype = float )
141+ PBIS = np .fromiter (map (lambda solution : penalty_based_intersection (solution , weights [most_crowded_domain ], best_obj , penalty_factor , levels .normalizer ),
142+ worst_NDL_section ), dtype = float )
131143 return worst_NDL_section [np .argmax (PBIS )]
132144
133145
134146class PopulationUpdater (CompoundOperator ):
135147 key = 'PopulationUpdater'
136148
137- def apply (self , objective : ParetoLevels , arguments : dict ):
149+ def apply (self , objective : Tuple [ Union [ SoEq , ParetoLevels ]] , arguments : dict ):
138150 '''
139151 Update population to get the pareto-nondomiated levels with the worst element removed.
140152 Here, "worst" means the solution with highest PBI value (penalty-based boundary intersection)
141- '''
153+ '''
154+ assert isinstance (objective , tuple ), f'Expected input of PopulationUpdater to be a Tuple of SoEq and ParetoLevels.\n ' \
155+ f'Did not get even a Tuple, instead got { type (objective )} !'
156+ assert isinstance (objective [0 ], SoEq ), f'Expected input of PopulationUpdater to be a Tuple of SoEq and ParetoLevels.\n ' \
157+ f'Did not get a SoEq obj in the first position, instead got { type (objective [0 ])} !'
158+ assert isinstance (objective [1 ], ParetoLevels ), f'Expected input of PopulationUpdater to be a Tuple of SoEq and ParetoLevels.\n ' \
159+ f'Did not get even a ParetoLevels in the second position, ' \
160+ f'instead got { type (objective [1 ])} !.'
161+
142162 self_args , subop_args = self .parse_suboperator_args (arguments = arguments )
143163 # print(f'PopulationUpdater.params is {self.params}')
144164
165+ # TODO: Init normalizer here!
166+ # print('objective is ', objective)
167+ # objective[1].set_normalizer()
168+
145169 objective [1 ].update (objective [0 ]) #levels_updated = ndl_update(offspring, levels)
146170 if len (objective [1 ].levels ) == 1 :
147171 worst_solution = locate_pareto_worst (objective [1 ], self_args ['weights' ],
@@ -168,7 +192,8 @@ def apply(self, objective : ParetoLevels, arguments : dict):
168192 else :
169193 PBI = lambda domain_idx : np .sum ([penalty_based_intersection (sol_obj , self_args ['weights' ][domain_idx ],
170194 self_args ['best_obj' ],
171- self .params ['PBI_penalty' ])
195+ self .params ['PBI_penalty' ],
196+ objective [1 ].normalizer )
172197 for sol_obj in last_level_by_domains [domain_idx ]])
173198 PBIS = np .fromiter (map (PBI , crowded_domains ), dtype = float )
174199 most_crowded_domain = crowded_domains [np .argmax (PBIS )]
@@ -179,7 +204,8 @@ def apply(self, objective : ParetoLevels, arguments : dict):
179204 else :
180205 PBIS = np .fromiter (map (lambda solution : penalty_based_intersection (solution ,
181206 self_args ['weights' ][most_crowded_domain ],
182- self_args ['best_obj' ], self .params ['PBI_penalty' ]),
207+ self_args ['best_obj' ], self .params ['PBI_penalty' ],
208+ objective [1 ].normalizer ),
183209 last_level_by_domains [most_crowded_domain ]), dtype = float )
184210 worst_solution = last_level_by_domains [most_crowded_domain ][np .argmax (PBIS )]
185211
@@ -242,7 +268,8 @@ def apply(self, objective : ParetoLevels, arguments : dict):
242268 most_crowded_domain = crowded_domains [0 ]
243269 else :
244270 PBI = lambda domain_idx : np .sum ([penalty_based_intersection (sol_obj , self_args ['weights' ][domain_idx ],
245- self_args ['best_obj' ], self .params ['PBI_penalty' ])
271+ self_args ['best_obj' ], self .params ['PBI_penalty' ],
272+ objective .normalizer )
246273 for sol_obj in last_level_by_domains [domain_idx ]])
247274 PBIS = np .fromiter (map (PBI , crowded_domains ), dtype = float )
248275 most_crowded_domain = crowded_domains [np .argmax (PBIS )]
@@ -417,7 +444,7 @@ def apply(self, objective : ParetoLevels, arguments : dict):
417444
418445 '''
419446 self_args , subop_args = self .parse_suboperator_args (arguments = arguments )
420-
447+
421448 if len (objective .population ) == 0 :
422449 for idx , candidate in enumerate (objective .unplaced_candidates ):
423450 self .suboperators ['right_part_selector' ].apply (objective = candidate ,
@@ -432,6 +459,10 @@ def apply(self, objective : ParetoLevels, arguments : dict):
432459 arguments = subop_args ['chromosome_fitness' ])
433460 objective .history .add (tuple (candidate .obj_fun ))
434461 objective .initial_placing ()
462+
463+ # TODO: consider carefully, where normalizer init shall be held. If here, only the initial values are employed
464+ objective .set_normalizer ()
465+
435466 return objective
436467
437468def get_initial_sorter (right_part_selector : CompoundOperator ,
0 commit comments