@@ -27,7 +27,6 @@ def generate_pareto_front(
2727 """
2828 MOEA/D-AWA with optional adaptive bounds and weight adjustment.
2929 """
30- num_assets = len (kwargs ["expected_returns" ])
3130 num_objectives = len (self .problem .objectives )
3231
3332 # ── 1. Weight vectors & neighbourhood ────────────────────────────────
@@ -38,42 +37,10 @@ def generate_pareto_front(
3837 # ── 2. Initialize Normalisation ──────────────────────────────────────
3938 self ._init_normalization (kwargs )
4039
41- def repair (w ):
42- if repair_method == "euclidean" :
43- return self ._repair (w )
44- elif repair_method == "simple" :
45- return self ._repair_simple (w )
46- else :
47- return w
48-
4940 # ── 3. Initial Population ─────────────────────────────────────────────
50- candidates = []
51- for j in range (num_assets ):
52- w = np .zeros (num_assets )
53- w [j ] = 1.0
54- candidates .append (w )
55- candidates .append (np .ones (num_assets ) / num_assets )
56- candidates .extend (
57- np .random .dirichlet (np .ones (num_assets ), max (100 , 2 * num_points ))
58- )
59- cand_arr = np .array (candidates )
60- cand_f = np .array (
61- [list (self .problem .evaluate_all (w , ** kwargs ).values ()) for w in cand_arr ]
41+ population , f_phys , f_norm = self ._initial_sampling (
42+ weight_vectors , reference_type , ** kwargs
6243 )
63- cand_fn = np .array ([self ._normalise (f ) for f in cand_f ])
64-
65- population = np .empty ((num_points , num_assets ))
66- f_phys = np .empty ((num_points , num_objectives ))
67- f_norm = np .empty ((num_points , num_objectives ))
68-
69- for k , wv in enumerate (weight_vectors ):
70- g_vals = np .array (
71- [self ._get_scalar_value (fn , wv , reference_type ) for fn in cand_fn ]
72- )
73- best = int (np .argmin (g_vals ))
74- population [k ] = cand_arr [best ]
75- f_phys [k ] = cand_f [best ]
76- f_norm [k ] = cand_fn [best ]
7744
7845 # Elite Population (EP) for AWA
7946 ep_weights = population .copy ()
@@ -91,48 +58,35 @@ def repair(w):
9158 nb_idx = neighbors [i ]
9259 p1_idx , p2_idx = np .random .choice (nb_idx , 2 , replace = False )
9360
94- # Generate offspring
95- if crossover_operator == "sbx" :
96- offspring = self ._sbx_crossover (
97- population [p1_idx ], population [p2_idx ]
98- )
99- offspring = repair (offspring )
100- elif crossover_operator == "simplex" :
101- offspring = self ._simplex_crossover (
102- population [p1_idx ], population [p2_idx ]
103- )
104- elif crossover_operator == "ldc" :
105- offspring = self ._ldc_crossover (
106- population [p1_idx ], population [p2_idx ]
107- )
108- else :
109- offspring = population [p1_idx ].copy ()
110-
111- if mutation_operator == "polynomial" :
112- offspring = self ._polynomial_mutation (offspring )
113- offspring = repair (offspring )
114- elif mutation_operator == "simplex" :
115- offspring = self ._simplex_mutation (offspring , eta = mutation_eta )
61+ # Generate offspring using shared operators
62+ offspring = self ._apply_genetic_operators (
63+ population [p1_idx ],
64+ population [p2_idx ],
65+ crossover_operator ,
66+ mutation_operator ,
67+ mutation_eta ,
68+ repair_method ,
69+ )
11670
71+ # Evaluate offspring
11772 off_f = np .array (
11873 list (self .problem .evaluate_all (offspring , ** kwargs ).values ())
11974 )
12075 off_fn = self ._normalise (off_f )
12176
122- # Update neighbors
123- count = 0
124- for j in nb_idx :
125- if count >= nr :
126- break
127- wv_j = weight_vectors [j ]
128- curr_g = self ._get_scalar_value (f_norm [j ], wv_j , reference_type )
129- off_g = self ._get_scalar_value (off_fn , wv_j , reference_type )
130-
131- if off_g < curr_g :
132- population [j ] = offspring
133- f_phys [j ] = off_f
134- f_norm [j ] = off_fn
135- count += 1
77+ # Update neighbors using shared logic
78+ self ._update_neighborhood (
79+ offspring ,
80+ off_f ,
81+ off_fn ,
82+ nb_idx ,
83+ nr ,
84+ weight_vectors ,
85+ population ,
86+ f_phys ,
87+ f_norm ,
88+ reference_type ,
89+ )
13690
13791 # ── 5. Update Elite Population (EP) ───────────────────────────
13892 # Check if offspring is non-dominated by current EP
@@ -290,20 +244,3 @@ def repair(w):
290244 if kwargs .get ("record_history" , False ):
291245 return metrics , population , weight_vectors , history
292246 return metrics , population , weight_vectors
293-
294- def _get_non_dominated_indices (self , objectives ):
295- num_solutions = len (objectives )
296- if num_solutions <= 1 :
297- return np .ones (num_solutions , dtype = bool )
298-
299- is_efficient = np .ones (num_solutions , dtype = bool )
300- for i in range (num_solutions ):
301- if is_efficient [i ]:
302- c = objectives [i ]
303- # A solution j is dominated by c if all(c <= j) and any(c < j)
304- # Set is_efficient[j] to False if c dominates j
305- is_dominated = np .all (c <= objectives , axis = 1 ) & np .any (
306- c < objectives , axis = 1
307- )
308- is_efficient [is_dominated ] = False
309- return is_efficient
0 commit comments