def __call__(self, g): """return `list` of AL penalties for constraints values in `g`. Penalties are zero in the optimum and can be negative down to ``-lam**2 / mu / 2``. """ if self.lam is None: return [0.] * len(g) assert len(self.lam) == len(self.mu) if len(g) != len(self.lam): raise ValueError("len(g) = %d != %d = # of Lagrange coefficients" % (len(g), len(self.lam))) assert self._equality.dtype == 'bool' idx = _and(_not(self._equality), self.mu * g < -1 * self.lam) if any(idx): g = np.array(g, copy=True) g[idx] = -self.lam[idx] / self.mu[idx] return [self.lam[i] * g[i] + 0.5 * self.mu[i] * g[i]**2 for i in range(len(g))]
def set_coefficients(self, F, G): """compute initial coefficients based on some f- and g-values. The formulas to set the coefficients:: lam = iqr(f) / (sqrt(n) * iqr(g)) mu = 2 * iqr(f) / (5 * n * (iqr(g) + iqr(g**2))) are taken out of thin air and not thoroughly tested. They are additionally protected against division by zero. Each row of `G` represents the constraints of one sample measure. Set lam and mu until a population contains more than 10% infeasible and more than 10% feasible at the same time. Afterwards, this at least...?... """ self.F, self.G = F, G # versatile storage if self.mu is not None and all(self._initialized * (self.mu > 0)): return # we're all set G = np.asarray(G).T # now row G[i] contains all values of constraint i sign_average = np.mean(np.sign(G), axis=1) # caveat: equality vs inequality if self.lam is None: # destroys all coefficients self.set_m(len(G)) elif not self._initialized.shape: self.lam_old, self.mu_old = self.lam[:], self.mu[:] # in case the user didn't mean to self._initialized = np.array(self.m * [self._initialized]) self._check_dtypes() idx = _and(_or(_not(self._initialized), self.mu == 0), _or(sign_average > -0.8, self.isequality)) # print(len(F), len(self.G)) if np.any(idx): df = _Mh.iqr(F) dG = np.asarray([_Mh.iqr(g) for g in G]) # only needed for G[idx], but like dG2 = np.asarray([_Mh.iqr(g**2) for g in G]) # this simpler in later indexing if np.any(df == 0) or np.any(dG == 0) or np.any(dG2 == 0): _warnings.warn("iqr(f), iqr(G), iqr(G**2)) == %s, %s, %s" % (str(df), str(dG), str(dG2))) assert np.all(df >= 0) and np.all(dG >= 0) and np.all(dG2 >= 0) # 1 * dG2 leads to much too small values for Himmelblau mu_new = 2. / 5 * df / self.dimension / ( dG + 1e-6 * dG2 + 1e-11 * (df + 1)) # TODO: totally out of thin air idx_inequ = _and(idx, _not(self.isequality)) if np.any(idx_inequ): self.lam[idx_inequ] = df / (self.dimension * dG[idx_inequ] + 1e-11 * (df + 1)) # take min or max with existing value depending on the sign average # we don't know whether this is necessary isclose = np.abs(sign_average) <= 0.2 idx1 = _and( _and( _or(isclose, _and(_not(self.isequality), sign_average <= 0.2)), _or(self.mu == 0, self.mu > mu_new)), idx) # only decrease idx2 = _and( _and( _and(_not(isclose), _or(self.isequality, sign_average > 0.2)), self.mu < mu_new), idx) # only increase idx3 = _and(idx, _not(_or(idx1, idx2))) # others assert np.sum(_and(idx1, idx2)) == 0 if np.any(idx1): # decrease iidx1 = self.mu[idx1] > 0 if np.any(iidx1): self.lam[idx1][ iidx1] *= mu_new[idx1][iidx1] / self.mu[idx1][iidx1] self.mu[idx1] = mu_new[idx1] if np.any(idx2): # increase self.mu[idx2] = mu_new[idx2] if np.any(idx3): self.mu[idx3] = mu_new[idx3] if 11 < 3: # simple version, this may be good enough self.mu[idx] = mu_new[idx] self._initialized[_and( idx, _or( self.count > 2 + self.dimension, # in case sign average remains 1 np.abs(sign_average) < 0.8))] = True elif all(self._initialized) and all(self.mu > 0): _warnings.warn( "Coefficients are already fully initialized. This can (only?) happen if\n" "the coefficients are set before the `_initialized` array.")