@@ -44,7 +44,7 @@ def safe_function(x):
4444 safe_seeds = (np.random.rand(safe_seeds_num, dim) * 2 - 1) * 5
4545 safe_seeds[:, 0] = - np.abs(safe_seeds[:, 0])
4646
47- # evaluation of safe seeds (with multiple safety functions )
47+ # evaluation of safe seeds (with a single safety function )
4848 seeds_evals = np.array([quadratic(x) for x in safe_seeds])
4949 seeds_safe_evals = np.stack([[safe_function(x)] for x in safe_seeds])
5050 safety_threshold = np.array([0])
@@ -121,6 +121,8 @@ def safe_function(x):
121121 A covariance matrix (optional).
122122 """
123123
124+ # Paper: https://arxiv.org/abs/2405.10534v1
125+
124126 def __init__ (
125127 self ,
126128 safe_seeds : np .ndarray ,
@@ -147,7 +149,7 @@ def __init__(
147149 assert n_dim > 1 , "The dimension of mean must be larger than 1"
148150
149151 if population_size is None :
150- population_size = 4 + math .floor (3 * math .log (n_dim )) # (eq. 48)
152+ population_size = 4 + math .floor (3 * math .log (n_dim ))
151153 assert population_size > 0 , "popsize must be non-zero positive value."
152154
153155 mu = population_size // 2
@@ -193,16 +195,16 @@ def __init__(
193195 assert c1 <= 1 - cmu , "invalid learning rate for the rank-one update"
194196 assert cmu <= 1 - c1 , "invalid learning rate for the rank-μ update"
195197
196- cm = 1 # (eq. 54)
198+ cm = 1
197199
198- # learning rate for the cumulation for the step-size control (eq.55)
200+ # learning rate for the cumulation for the step-size control
199201 c_sigma = (mu_eff + 2 ) / (n_dim + mu_eff + 5 )
200202 d_sigma = 1 + 2 * max (0 , math .sqrt ((mu_eff - 1 ) / (n_dim + 1 )) - 1 ) + c_sigma
201203 assert (
202204 c_sigma < 1
203205 ), "invalid learning rate for cumulation for the step-size control"
204206
205- # learning rate for cumulation for the rank-one update (eq.56)
207+ # learning rate for cumulation for the rank-one update
206208 cc = (4 + mu_eff / n_dim ) / (n_dim + 4 + 2 * mu_eff / n_dim )
207209 assert cc <= 1 , "invalid learning rate for cumulation for the rank-one update"
208210
@@ -218,7 +220,7 @@ def __init__(
218220 self ._d_sigma = d_sigma
219221 self ._cm = cm
220222
221- # E||N(0, I)|| (p.28)
223+ # E||N(0, I)||
222224 self ._chi_n = math .sqrt (self ._n_dim ) * (
223225 1.0 - (1.0 / (4.0 * self ._n_dim )) + 1.0 / (21.0 * (self ._n_dim ** 2 ))
224226 )
@@ -270,7 +272,6 @@ def __init__(
270272 self ._funhist_values = np .empty (self ._funhist_term * 2 )
271273
272274 def _compute_lipschitz_constant (self ) -> np .ndarray :
273-
274275 likelihood = gpytorch .likelihoods .GaussianLikelihood (
275276 noise_constraint = gpytorch .constraints .GreaterThan (0 )
276277 )
@@ -288,9 +289,8 @@ def _compute_lipschitz_constant(self) -> np.ndarray:
288289 evals_std = np .std (target_safe_evals , axis = 0 )
289290 modified_evals = (target_safe_evals - evals_mean ) / evals_std
290291
291- # function that returns the norm of gradient
292+ # function that returns the negative norm of gradient
292293 def df (x : np .ndarray , model : ExactGPModel ) -> torch .Tensor :
293-
294294 out_scalar = x .ndim == 1
295295 x = np .atleast_2d (x )
296296
@@ -403,7 +403,7 @@ def _init_distribution(self, sigma: float) -> tuple[np.ndarray, float]:
403403 # set initial mean vector
404404 best_seed_id = np .argmin (self .seeds_evals )
405405 mean = self .safe_seeds [best_seed_id ]
406- self ._mean = mean .copy ()
406+ self ._mean = mean .copy () # (eq. 26)
407407
408408 # set initial step-size
409409 if len (self .sampled_points ) > 1 :
@@ -422,7 +422,7 @@ def _init_distribution(self, sigma: float) -> tuple[np.ndarray, float]:
422422 slack = self .safety_threshold - self .seeds_safe_evals [best_seed_id ]
423423 delta = np .min ((slack ) / self .lipschitz_constant )
424424 gauss_tr = np .sqrt (scipy .stats .chi2 .ppf (self .gamma , df = self ._n_dim ))
425- sigma = sigma * np .min ((delta / gauss_tr , 1 ))
425+ sigma = sigma * np .min ((delta / gauss_tr , 1 )) # (eq. 27)
426426
427427 return mean , sigma
428428
@@ -461,7 +461,9 @@ def _sample_solution(self) -> np.ndarray:
461461
462462 # radius: radius of trust region around evaluated points
463463 slack = self .safety_threshold [:, None , None ] - prev_safe_evals [None , :, :]
464- radius = np .min (slack / self .lipschitz_constant [:, None , None ], axis = (0 , 2 ))
464+ radius = np .min (
465+ slack / self .lipschitz_constant [:, None , None ], axis = (0 , 2 )
466+ ) # (eq.13)
465467
466468 radius [radius < 0 ] = - np .inf
467469 # dist: distance between current samples and evaluated points
@@ -472,7 +474,7 @@ def _sample_solution(self) -> np.ndarray:
472474 closest_z_sample = sampled_z_points [argmin_sample_id ]
473475
474476 ratio = invalid_dist / dist [argmin_sample_id ]
475- z = (1 - ratio ) * z + ratio * closest_z_sample
477+ z = (1 - ratio ) * z + ratio * closest_z_sample # (eq.15)
476478
477479 y = cast (np .ndarray , B .dot (np .diag (D )).dot (B .T )).dot (z ) # ~ N(0, C)
478480 x = self ._mean + self ._sigma * y # ~ N(m, σ^2 C)
@@ -496,17 +498,16 @@ def _repair_infeasible_params(self, param: np.ndarray) -> np.ndarray:
496498 return param
497499
498500 def tell (self , solutions : list [tuple [np .ndarray , float , float ]]) -> None :
499-
500501 self ._naive_cma_update (solutions )
501502
502503 X = np .stack ([s [0 ] for s in solutions ])
503504 safe_evals = np .array ([s [2 ] for s in solutions ])
504505
505506 self ._add_evaluated_point (X , safe_evals )
506507
507- self .lipschitz_constant = self ._compute_lipschitz_constant ()
508+ self .lipschitz_constant = self ._compute_lipschitz_constant () # (eq.19)
508509 if len (self .sampled_safe_evals ) < self .sample_num_lip :
509- exponent = 1 / len (self .sampled_safe_evals )
510+ exponent = 1 / len (self .sampled_safe_evals ) # (eq.22)
510511 self .lipschitz_constant *= self .init_L_base ** exponent
511512
512513 inv_num = np .sum (safe_evals > self .safety_threshold , dtype = np .float32 )
@@ -517,7 +518,7 @@ def tell(self, solutions: list[tuple[np.ndarray, float, float]]) -> None:
517518 else :
518519 self .lip_penalty_coef /= self .lip_penalty_dec_rate
519520 self .lip_penalty_coef = np .max ((self .lip_penalty_coef , 1 ))
520- self .lipschitz_constant *= self .lip_penalty_coef
521+ self .lipschitz_constant *= self .lip_penalty_coef # (eq.24)
521522
522523 def _add_evaluated_point (self , X : np .ndarray , safe_evals : np .ndarray ) -> None :
523524 self .sampled_points = np .concatenate ([self .sampled_points , X ], axis = 0 )
@@ -551,8 +552,8 @@ def _naive_cma_update(
551552 y_k = (x_k - self ._mean ) / self ._sigma # ~ N(0, C)
552553
553554 # Selection and recombination
554- y_w = np .sum (y_k [: self ._mu ].T * self ._weights [: self ._mu ], axis = 1 ) # eq.41
555- self ._mean += self ._cm * self ._sigma * y_w
555+ y_w = np .sum (y_k [: self ._mu ].T * self ._weights [: self ._mu ], axis = 1 )
556+ self ._mean += self ._cm * self ._sigma * y_w # (eq.7)
556557
557558 # Step-size control
558559 C_2 = cast (
@@ -565,28 +566,26 @@ def _naive_cma_update(
565566 norm_p_sigma = np .linalg .norm (self ._p_sigma )
566567 self ._sigma *= np .exp (
567568 (self ._c_sigma / self ._d_sigma ) * (norm_p_sigma / self ._chi_n - 1 )
568- )
569+ ) # (eq.8)
569570 self ._sigma = min (self ._sigma , _SIGMA_MAX )
570571
571572 # Covariance matrix adaption
572573 h_sigma_cond_left = norm_p_sigma / math .sqrt (
573574 1 - (1 - self ._c_sigma ) ** (2 * (self ._g + 1 ))
574575 )
575576 h_sigma_cond_right = (1.4 + 2 / (self ._n_dim + 1 )) * self ._chi_n
576- h_sigma = 1.0 if h_sigma_cond_left < h_sigma_cond_right else 0.0 # (p.28)
577+ h_sigma = 1.0 if h_sigma_cond_left < h_sigma_cond_right else 0.0
577578
578- # (eq.45)
579579 self ._pc = (1 - self ._cc ) * self ._pc + h_sigma * math .sqrt (
580580 self ._cc * (2 - self ._cc ) * self ._mu_eff
581581 ) * y_w
582582
583- # (eq.47)
584583 rank_one = np .outer (self ._pc , self ._pc )
585584 rank_mu = np .sum (
586585 np .array ([w * np .outer (y , y ) for w , y in zip (self ._weights , y_k )]), axis = 0
587586 )
588587
589- delta_h_sigma = (1 - h_sigma ) * self ._cc * (2 - self ._cc ) # (p.28)
588+ delta_h_sigma = (1 - h_sigma ) * self ._cc * (2 - self ._cc )
590589 assert delta_h_sigma <= 1
591590
592591 self ._C = (
@@ -599,7 +598,7 @@ def _naive_cma_update(
599598 * self ._C
600599 + self ._c1 * rank_one
601600 + self ._cmu * rank_mu
602- )
601+ ) # (eq.9)
603602
604603 def should_stop (self ) -> bool :
605604 B , D = self ._eigen_decomposition ()
@@ -686,7 +685,6 @@ def __init__(
686685 likelihood : gpytorch .likelihoods .Likelihood ,
687686 kernel : gpytorch .kernels .Kernel ,
688687 ) -> None :
689-
690688 super (ExactGPModel , self ).__init__ (
691689 torch .from_numpy (train_x ), torch .from_numpy (train_y ), likelihood
692690 )
0 commit comments