1111**EpdeSearch** class for main interactions between the user and the framework.
1212
1313"""
14+ import inspect
1415import pickle
1516import numpy as np
1617import torch
@@ -241,7 +242,7 @@ def __init__(self, multiobjective_mode: bool = True, use_pic = True, use_default
241242 pivotal_tensor_label = None , pruner = None , threshold : float = 1e-2 ,
242243 division_fractions = 3 , rectangular : bool = True ,
243244 params_filename : str = None , device : str = 'cpu' ,
244- fitness_cls = None , sparsity_cls = None ):
245+ discrepancy_metric : str = 'wape' , sparsity_cls = None ):
245246 """
246247 Args:
247248 multiobjective_mode (`bool`): optional, default True
@@ -292,6 +293,7 @@ def __init__(self, multiobjective_mode: bool = True, use_pic = True, use_default
292293 self ._device = device
293294 self .multiobjective_mode = multiobjective_mode
294295 self ._use_pic = use_pic
296+ self ._discrepancy_metric = discrepancy_metric
295297
296298 global_var .set_time_axis (time_axis )
297299 global_var .init_verbose (** verbose_params )
@@ -324,7 +326,7 @@ def __init__(self, multiobjective_mode: bool = True, use_pic = True, use_default
324326 self .director .builder = builder
325327 self .director .use_baseline (use_solver = self ._mode_info ['solver_fitness' ],
326328 use_pic = self ._use_pic , params = director_params ,
327- fitness_cls = fitness_cls , sparsity_cls = sparsity_cls )
329+ discrepancy_metric = discrepancy_metric , sparsity_cls = sparsity_cls )
328330 else :
329331 raise NotImplementedError ('Wrong arguments passed during the epde search initialization' )
330332
@@ -361,8 +363,8 @@ def set_moeadd_params(self, population_size: int = 6, solution_params: dict = {}
361363 H : int = 15 , neighbors_number : int = 3 ,
362364 nds_method : Callable = fast_non_dominated_sorting ,
363365 ndl_update_method : Callable = ndl_update ,
364- subregion_mating_limitation : float = .95 ,
365- PBI_penalty : float = 1 . , training_epochs : int = 100 ,
366+ subregion_mating_limitation : float = .9 ,
367+ PBI_penalty : float = 5 . , training_epochs : int = 100 ,
366368 neighborhood_selector : Callable = simple_selector ,
367369 neighborhood_selector_params : tuple = (4 ,),
368370 early_stopping_callback : Callable = None ):
@@ -378,6 +380,10 @@ def set_moeadd_params(self, population_size: int = 6, solution_params: dict = {}
378380 H (`float`): optional
379381 parameter of uniform spacing between the weight vectors; *H = 1 / delta*
380382 should be integer - a number of divisions along an objective coordinate axis.
383+ NOTE: currently ignored — the optimizer always uses *H = population_size - 1*,
384+ which (for the two-objective weight space used by EPDE) keeps the number of
385+ Das-Dennis weight vectors equal to the population size, as the MOEA/DD paper
386+ requires (N solutions <-> N weight vectors).
381387 neighbors_number (`int`): *> 0*, optional
382388 number of neighboring weight vectors to be considered during the operation
383389 of evolutionary operators as the "neighbors" of the processed sectors.
@@ -394,36 +400,47 @@ def set_moeadd_params(self, population_size: int = 6, solution_params: dict = {}
394400 Dept. Electr. Comput. Eng., Michigan State Univ., East Lansing,
395401 MI, USA, Tech. Rep. COIN No. 2014014, 2014.*
396402 neighborhood_selector (`callable`): optional
397- Method of finding "close neighbors" of the vector with proximity list.
398- The baseline example of the selector, presented in
399- ``moeadd.moeadd_stc.simple_selector``, selects n-adjacent ones .
403+ DEPRECATED, ignored. Neighboring weight vectors are chosen by the
404+ ``SimpleNeighborSelector`` operator, which randomly picks indices
405+ from the proximity list E(i), per Algorithm 3 of the MOEA/DD paper .
400406 subregion_mating_limitation (`float`): optional
401407 The probability of mating selection to be limited only to the selected
402- subregions (adjacent to the weight vector domain).:math:`\delta \in [0., 1.)
408+ subregions (adjacent to the weight vector domain). :math:`\delta \in [0., 1.)`,
409+ default value is 0.9, as in the MOEA/DD paper.
403410 neighborhood_selector_params (`tuple|list`): optional
404- Iterable, which will be passed into neighborhood_selector, as
405- an arugument. *None*, is no additional arguments are required inside
406- the selector.
411+ DEPRECATED, ignored (see ``neighborhood_selector``).
407412 training_epochs (`int`): optional
408413 Maximum number of iterations, during that the optimization will be held.
409414 Note, that if the algorithm converges to a single Pareto frontier,
410415 the optimization is stopped.
411416 PBI_penalty (`float`): optional
412- The penalty parameter, used in penalty based intersection
413- calculation, defalut value is 1 .
417+ The penalty parameter :math:`\\theta` , used in penalty based intersection
418+ calculation, default value is 5.0, as in the MOEA/DD paper .
414419
415420 Returns:
416421 None
417422 """
418423 self .optimizer_init_params = {'pop_size' : population_size ,
419424 'H' : population_size - 1 , 'neighbors_number' : neighbors_number ,
420425 'solution_params' : solution_params ,
421- 'nds_method' : nds_method ,
426+ 'nds_method' : nds_method ,
422427 'ndl_update' : ndl_update_method }
423-
428+
424429 self .optimizer_exec_params = {'epochs' : training_epochs ,
425430 'early_stopping_callback' : early_stopping_callback }
426431
432+ # Forward the user-facing MOEA/DD parameters to the operators of the
433+ # strategy assembled in __init__ (previously these arguments were
434+ # accepted but silently dropped, so the JSON defaults always applied).
435+ director = getattr (self , 'director' , None )
436+ if director is not None and director .builder is not None :
437+ blocks = director .builder .blocks_labeled
438+ if 'selection' in blocks :
439+ blocks ['selection' ]._operator .params ['delta' ] = subregion_mating_limitation
440+ if 'pareto_updater_compl' in blocks :
441+ pareto_updater = blocks ['pareto_updater_compl' ]._operator
442+ pareto_updater .suboperators ['pareto_level_updater' ].params ['PBI_penalty' ] = PBI_penalty
443+
427444 def set_singleobjective_params (self , population_size : int = 4 , solution_params : dict = {},
428445 sorting_method : Callable = simple_sorting , training_epochs : int = 50 ):
429446 """
@@ -851,7 +868,13 @@ def fit(self, data: Union[np.ndarray, list, tuple] = None, equation_terms_max_nu
851868 else :
852869 self .optimizer = optimizer
853870
854- self .optimizer .optimize (** self .optimizer_exec_params )
871+ # Pass only the exec params this optimizer's ``optimize`` accepts:
872+ # SimpleOptimizer.optimize has no ``early_stopping_callback`` (a
873+ # MOEA/D-only exec param that set_moeadd_params leaves behind when an
874+ # EpdeSearch built multiobjective-by-default is run single-objective).
875+ _exec_keys = set (inspect .signature (self .optimizer .optimize ).parameters ) - {'self' }
876+ _exec_params = {k : v for k , v in self .optimizer_exec_params .items () if k in _exec_keys }
877+ self .optimizer .optimize (** _exec_params )
855878
856879 print ('The optimization has been conducted.' )
857880 self .search_conducted = True
@@ -875,9 +898,17 @@ def _create_optimizer(multiobjective_mode: bool, optimizer_init_params: dict,
875898 optimizer .pass_best_objectives (* best_sol_vals )
876899 else :
877900 optimizer_init_params ['passed_population' ] = population
878- optimizer = SimpleOptimizer (** optimizer_init_params )
879-
880- optimizer .set_strategy (opt_strategy_director )
901+ # Pass only the params SimpleOptimizer accepts. ``optimizer_init_params``
902+ # can still carry MOEA/D-only keys (e.g. ``H``, ``nds_method``) if
903+ # ``set_moeadd_params`` ran earlier -- which it does in EpdeSearch's
904+ # default (multiobjective) __init__ before a switch to single-objective.
905+ # Selecting by SimpleOptimizer's signature keeps this robust to how the
906+ # params dict was populated.
907+ so_keys = set (inspect .signature (SimpleOptimizer .__init__ ).parameters ) - {'self' }
908+ so_params = {k : v for k , v in optimizer_init_params .items () if k in so_keys }
909+ optimizer = SimpleOptimizer (** so_params )
910+
911+ optimizer .set_strategy (opt_strategy_director )
881912 return optimizer
882913
883914 @property
@@ -1068,7 +1099,7 @@ def visualize_solutions(self, dimensions:list = [0, 1], **visulaizer_kwargs) ->
10681099 equations from the population. Furthermore, the annotate of the candidate equations are made with LaTeX toolkit.
10691100 '''
10701101 if self .multiobjective_mode :
1071- self .optimizer .plot_pareto (dimensions = dimensions , ** visulaizer_kwargs )
1102+ return self .optimizer .plot_pareto (dimensions = dimensions , ** visulaizer_kwargs )
10721103 else :
10731104 raise NotImplementedError ('Solution visualization is implemented only for multiobjective mode.' )
10741105
@@ -1442,7 +1473,13 @@ def fit(self, samples: List[Tuple], equation_terms_max_number=6, equation_factor
14421473 else :
14431474 self .optimizer = optimizer
14441475
1445- self .optimizer .optimize (** self .optimizer_exec_params )
1476+ # Pass only the exec params this optimizer's ``optimize`` accepts:
1477+ # SimpleOptimizer.optimize has no ``early_stopping_callback`` (a
1478+ # MOEA/D-only exec param that set_moeadd_params leaves behind when an
1479+ # EpdeSearch built multiobjective-by-default is run single-objective).
1480+ _exec_keys = set (inspect .signature (self .optimizer .optimize ).parameters ) - {'self' }
1481+ _exec_params = {k : v for k , v in self .optimizer_exec_params .items () if k in _exec_keys }
1482+ self .optimizer .optimize (** _exec_params )
14461483
14471484 print ('The optimization has been conducted.' )
14481485 self .search_conducted = True
0 commit comments