//+——————————————————————————————————————————————————————————————————+ //| C_AO_HHO | //| Copyright 2007-2025, Andrey Dik | //| https://www.mql5.com/ru/users/joo | //+——————————————————————————————————————————————————————————————————+ #include "#C_AO.mqh" //──────────────────────────────────────────────────────────────────── class C_AO_HHO : public C_AO { public: //------------------------------------------------------------ ~C_AO_HHO () { } C_AO_HHO () { ao_name = "HHO"; ao_desc = "Harris Hawks Optimization"; ao_link = "https://www.mql5.com/ru/articles/21898"; popSize = 50; beta = 1.5; ArrayResize (params, 2); params [0].name = "popSize"; params [0].val = popSize; params [1].name = "beta"; params [1].val = beta; } void SetParams () { popSize = (int)params [0].val; beta = params [1].val; } bool Init (const double &rangeMinP [], const double &rangeMaxP [], const double &rangeStepP [], const int epochsP = 0); void Moving (); void Revision (); //------------------------------------------------------------------ double beta; // показатель хвоста Леви (β = 1.5 в оригинале) private: //--------------------------------------------------------- int t; // текущая итерация (счёт с 1) int T; // всего итераций double sigma; // σ для формулы Леви (Eq. 9), вычисляется один раз в Init double Xm []; // [coords] среднее положение стаи (Eq. 2) double Ytmp []; // [coords] кандидат Y (стратегии 3, 4) double LGamma (double x); // логарифм Г(x), аппроксимация Ланцоша double LevyStep (); // [L2] скалярный шаг Леви: u~N(0,σ), v~N(0,1) double Clamp (double v, double lo, double hi) { return v < lo ? lo : (v > hi ? hi : v); } }; //———————————————————————————————————————————————————————————————————— //———————————————————————————————————————————————————————————————————— bool C_AO_HHO::Init (const double &rangeMinP [], const double &rangeMaxP [], const double &rangeStepP [], const int epochsP = 0) { if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false; //------------------------------------------------------------------ T = (epochsP > 0) ? epochsP : 1; t = 0; ArrayResize (Xm, coords); ArrayResize (Ytmp, coords); //--- Eq. (9): σ = [Г(1+β)·sin(πβ/2) / (Г((1+β)/2)·β·2^((β-1)/2))]^(1/β) double num = exp (LGamma (1.0 + beta)) * sin (M_PI * beta * 0.5); double den = exp (LGamma ((1.0 + beta) * 0.5)) * beta * pow (2.0, (beta - 1.0) * 0.5); sigma = (den > 1e-15) ? pow (fabs (num / den), 1.0 / beta) : 1.0; //--- начальные позиции: равномерно случайно в [LB, UB] for (int i = 0; i < popSize; i++) { for (int c = 0; c < coords; c++) { a [i].cP [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]); } a [i].fP = -DBL_MAX; // сброс истории улучшений для [L3] } return true; } //———————————————————————————————————————————————————————————————————— //———————————————————————————————————————————————————————————————————— void C_AO_HHO::Moving () { for (int i = 0; i < popSize; i++) { for (int c = 0; c < coords; c++) { a [i].c [c] = u.SeInDiSp (a [i].cP [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } } //———————————————————————————————————————————————————————————————————— //———————————————————————————————————————————————————————————————————— // Revision(): обновляет X_prey, X_m и вычисляет cP для следующего шага. // // Структура соответствует оригиналу: // |E| >= 1 → исследование: // q < 0.5 → X_rand formula (Eq. 1a) [L1] // q >= 0.5 → X_prey − X_m (Eq. 1b) // |E| < 1 → эксплуатация: // r>=0.5, |E|>=0.5 → стратегия 1: мягкая осада Eq. (4-5) // r>=0.5, |E|< 0.5 → стратегия 2: жёсткая осада Eq. (6) // r< 0.5, |E|>=0.5 → стратегия 3: мягкая осада + Lévy Eq. (7-10) // r< 0.5, |E|< 0.5 → стратегия 4: жёсткая осада + Lévy Eq. (11-13) // // [L3] В оригинале стратегии 3/4 делают два вызова fobj (для Y и Z). // Прокси в нашем фреймворке: если агент улучшился в прошлой итерации // (a[i].f > a[i].fP) — доверяем направленному шагу Y; // иначе добавляем Lévy → Z (как запасной вариант). void C_AO_HHO::Revision () { t++; //─── 1. Обновление личных и глобального лучших ────────────────── for (int i = 0; i < popSize; i++) { if (a [i].f > a [i].fB) { a [i].fB = a [i].f; ArrayCopy (a [i].cB, a [i].c, 0, 0, coords); } if (a [i].f > fB) { fB = a [i].f; ArrayCopy (cB, a [i].c, 0, 0, coords); } } //─── 2. Среднее положение стаи Xm (Eq. 2) ─────────────────────── ArrayInitialize (Xm, 0.0); for (int i = 0; i < popSize; i++) { for (int c = 0; c < coords; c++) { Xm [c] += a [i].c [c]; } } for (int c = 0; c < coords; c++) { Xm [c] /= (double)popSize; } //─── 3. E1 — общий для всей итерации ───────────── double E1 = 2.0 * (1.0 - (double)t / (double)T); //─── 4. Обновление позиции каждого ястреба ────────────────────── for (int i = 0; i < popSize; i++) { //--- [L3] Фиксируем признак улучшения ДО обновления fP bool improvedLast = (a [i].f > a [i].fP); a [i].fP = a [i].f; // сохраняем для следующей итерации //--- Eq. (3): E = E1·E0, E0 ~ U(−1,1) [E0 не нужен отдельно] double E = E1 * (2.0 * u.RNDfromCI (0.0, 1.0) - 1.0); double absE = fabs (E); if (absE >= 1.0) { //══ ИССЛЕДОВАНИЕ ══════════════════════════════════════════════ // q генерируется только здесь double q = u.RNDfromCI (0.0, 1.0); if (q < 0.5) { //─ [L1] Eq. (1a): ориентация на случайного члена стаи ────── // Xi(t+1) = X_rand − r1·|X_rand − 2·r2·Xi| int ri = u.RNDminusOne (popSize); double r1 = u.RNDfromCI (0.0, 1.0); double r2 = u.RNDfromCI (0.0, 1.0); for (int c = 0; c < coords; c++) { a [i].cP [c] = Clamp (a [ri].c [c] - r1 * fabs (a [ri].c [c] - 2.0 * r2 * a [i].c [c]), rangeMin [c], rangeMax [c]); } } else { //─ Eq. (1b): ориентация на разность X_prey − X_m ─────────── // Xi(t+1) = (X_prey − X_m) − r3·((UB−LB)·r4 + LB) double r3 = u.RNDfromCI (0.0, 1.0); double r4 = u.RNDfromCI (0.0, 1.0); for (int c = 0; c < coords; c++) { a [i].cP [c] = Clamp ((cB [c] - Xm [c]) - r3 * (rangeMin [c] + r4 * (rangeMax [c] - rangeMin [c])), rangeMin [c], rangeMax [c]); } } } else { //══ ЭКСПЛУАТАЦИЯ ══════════════════════════════════════════════ // r генерируется только здесь double r = u.RNDfromCI (0.0, 1.0); if (r >= 0.5 && absE >= 0.5) { //─ Стратегия 1: мягкая осада ───────────────────────────────── // ΔX = X_prey − Xi Eq. (5) // Xi(t+1) = ΔX − E·|J·X_prey − Xi| Eq. (4) // J генерируется здесь (Jump_strength) double J = 2.0 * (1.0 - u.RNDfromCI (0.0, 1.0)); for (int c = 0; c < coords; c++) { a [i].cP [c] = Clamp ((cB [c] - a [i].c [c]) - E * fabs (J * cB [c] - a [i].c [c]), rangeMin [c], rangeMax [c]); } } else { if (r >= 0.5 && absE < 0.5) { //─ Стратегия 2: жёсткая осада ──────────────────────────────── // Xi(t+1) = X_prey − E·|X_prey − Xi| Eq. (6) // J не используется в этой стратегии for (int c = 0; c < coords; c++) { a [i].cP [c] = Clamp (cB [c] - E * fabs (cB [c] - a [i].c [c]), rangeMin [c], rangeMax [c]); } } else { if (r < 0.5 && absE >= 0.5) { //─ Стратегия 3: мягкая осада + пикирование Леви ────────────── // Y = X_prey − E·|J·X_prey − Xi| Eq. (7) // Z = Y + rand·LF(D) Eq. (8) // Xi = Y если F(Y) 0. double C_AO_HHO::LGamma (double x) { static const double cL [8] = { 676.5203681218851, -1259.1392167224028, 771.32342877765313, -176.61502916214059, 12.507343278686905, -0.13857109526572012, 9.9843695780195716e-6, 1.5056327351493116e-7 }; if (x < 0.5) return log (M_PI / sin (M_PI * x)) - LGamma (1.0 - x); x -= 1.0; double ag = 0.99999999999980993; for (int k = 0; k < 8; k++) ag += cL [k] / (x + k + 1.0); double tt = x + 7.5; return 0.5 * log (2.0 * M_PI) + (x + 0.5) * log (tt) - tt + log (ag); } //————————————————————————————————————————————————————————————————————