Files
Population-optimization-alg…/MQL5/Include/Math/AOs/PopulationAO/AO_CoSO_CommunityOfScientistOptimization.mqh
T

848 lines
49 KiB
Plaintext

//+——————————————————————————————————————————————————————————————————+
//| C_AO_CoSO |
//| Copyright 2007-2025, Andrey Dik |
//| https://www.mql5.com/ru/users/joo |
//———————————————————————————————————————————————————————————————————+
//Article: https://www.mql5.com/ru/articles/18886
#include "#C_AO.mqh"
//————————————————————————————————————————————————————————————————————
// Структура для журнала
struct S_Journal_Entry
{
double fitness;
double decision [];
void Init (int coords)
{
ArrayResize (decision, coords);
fitness = -DBL_MAX;
}
};
// Структура журнала
struct S_Journal
{
S_Journal_Entry entries [];
int length;
int maxLength;
void Init (int maxLen, int coords)
{
maxLength = maxLen;
length = 0;
ArrayResize (entries, maxLen);
for (int i = 0; i < maxLen; i++)
{
entries [i].Init (coords);
entries [i].fitness = -DBL_MAX; // Инициализируем минимальным значением
}
}
void Add (double fit, const double &coord [])
{
// Быстрая проверка - если хуже всех и журнал полон, не добавляем
if (length >= maxLength && fit <= entries [length - 1].fitness) return;
int insertPos = length;
// Находим позицию для вставки (бинарный поиск)
if (length > 0)
{
int left = 0;
int right = length - 1;
while (left <= right)
{
int mid = (left + right) / 2;
if (entries [mid].fitness < fit) right = mid - 1;
else left = mid + 1;
}
insertPos = left;
}
// Если вставляем в конец и журнал полон
if (insertPos >= maxLength) return;
// Сдвигаем элементы
if (length < maxLength) length++;
for (int i = length - 1; i > insertPos; i--)
{
entries [i].fitness = entries [i - 1].fitness;
ArrayCopy (entries [i].decision, entries [i - 1].decision, 0, 0, WHOLE_ARRAY);
}
// Вставляем новый элемент
entries [insertPos].fitness = fit;
ArrayCopy (entries [insertPos].decision, coord, 0, 0, WHOLE_ARRAY);
}
};
// Расширенная структура исследователя
struct S_Researcher
{
double x []; // текущая позиция
double v []; // направление движения
double b []; // личный лучший результат
double rho []; // вероятности публикации в журналах
double s; // стратегия управления средствами
int m; // количество средств
double f; // fitness текущей позиции
double fb; // fitness лучшей позиции
bool alive; // флаг активности
void Init (int coords, int journalsNum)
{
if (ArraySize (x) != coords)
{
ArrayResize (x, coords);
ArrayResize (v, coords);
ArrayResize (b, coords);
}
if (ArraySize (rho) != journalsNum)
{
ArrayResize (rho, 0);
ArrayResize (rho, journalsNum);
}
f = -DBL_MAX;
fb = -DBL_MAX;
m = 0;
s = 0.5;
alive = true;
}
};
//————————————————————————————————————————————————————————————————————
class C_AO_CoSO : public C_AO
{
public: //----------------------------------------------------------
~C_AO_CoSO () { }
C_AO_CoSO ()
{
ao_name = "CoSO";
ao_desc = "Community of Scientist Optimization";
ao_link = "https://www.mql5.com/ru/articles/18886";
popSize = 10; // начальный размер популяции
totalFunds = 150; // общее количество средств
journalsNum = 3; // количество журналов
journalLen = 10; // длина журнала
omega = 0.7; // параметр инерции
ArrayResize (params, 5);
params [0].name = "popSize"; params [0].val = popSize;
params [1].name = "totalFunds"; params [1].val = totalFunds;
params [2].name = "journalsNum"; params [2].val = journalsNum;
params [3].name = "journalLen"; params [3].val = journalLen;
params [4].name = "omega"; params [4].val = omega;
//----------------------------------------------------------------
phi1 = 1.5; // когнитивный параметр
phi2 = 1.5; // социальный параметр
omegaMin = 0.2; // минимальный процент аутсайдеров
omegaMax = 0.5; // максимальный процент аутсайдеров
epsilonPlus = 0.2; // шаг увеличения разнообразия
epsilonMinus = 0.1; // шаг уменьшения разнообразия
}
void SetParams ()
{
popSize = (int)params [0].val;
totalFunds = (int)params [1].val;
journalsNum = (int)params [2].val;
journalLen = (int)params [3].val;
omega = params [4].val;
}
bool Init (const double &rangeMinP [],
const double &rangeMaxP [],
const double &rangeStepP [],
const int epochsP = 0);
void Moving ();
void Revision ();
//------------------------------------------------------------------
int totalFunds; // общее количество средств
int journalsNum; // количество журналов
int journalLen; // длина журнала
double omega; // параметр инерции
double phi1; // когнитивный параметр
double phi2; // социальный параметр
double omegaMin; // минимальный процент аутсайдеров
double omegaMax; // максимальный процент аутсайдеров
double epsilonPlus; // шаг увеличения разнообразия
double epsilonMinus; // шаг уменьшения разнообразия
private: //---------------------------------------------------------
S_Researcher researchers []; // массив исследователей
S_Journal journals []; // массив журналов
double omegaCurrent; // текущий процент аутсайдеров
double sigma0; // начальное стандартное отклонение
int actualPopSize; // текущий размер популяции
int maxPopSize; // максимальный размер популяции
double socialComponent []; // кэш для социального компонента
struct S_GlobalReport
{
double fitness;
int index;
};
S_GlobalReport globalReport [];
// Методы алгоритма
void UpdateDirection (int idx);
void SubmitToJournal (int idx);
void AssignFunds (int availableFunds);
void HireResearchers (int idx);
void CreateOutsiders (int outsiderFunds);
double ComputeStdDev ();
void UpdateOmega ();
int SelectJournal (const double &probs []);
void NormalizeProbabilities (double &probs []);
void CompactPopulation ();
};
//————————————————————————————————————————————————————————————————————
//————————————————————————————————————————————————————————————————————
bool C_AO_CoSO::Init (const double &rangeMinP [],
const double &rangeMaxP [],
const double &rangeStepP [],
const int epochsP = 0)
{
if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false;
//------------------------------------------------------------------
// Инициализация переменных состояния
actualPopSize = 0;
maxPopSize = 0;
sigma0 = 0;
omegaCurrent = omegaMin + (omegaMax - omegaMin) / 2.0;
// Проверка корректности параметров
if (totalFunds < popSize) totalFunds = popSize;
if (journalsNum < 1) journalsNum = 1;
if (journalLen < 1) journalLen = 1;
if (omega < 0.0) omega = 0.0;
if (omega > 1.0) omega = 1.0;
// Полная очистка от предыдущих запусков
ArrayResize (researchers, 0);
ArrayResize (journals, 0);
ArrayResize (globalReport, 0);
ArrayResize (socialComponent, 0);
// Сброс параметров
actualPopSize = 0;
maxPopSize = 0;
sigma0 = 0;
// Инициализация журналов
ArrayResize (journals, journalsNum);
for (int i = 0; i < journalsNum; i++)
{
journals [i].Init (journalLen, coords);
}
// Инициализация массива исследователей с запасом
maxPopSize = MathMin (popSize * 3, 300); // Ограничиваем максимальный размер
ArrayResize (researchers, maxPopSize);
ArrayResize (socialComponent, coords);
actualPopSize = popSize;
int fundsPerResearcher = totalFunds / popSize;
for (int i = 0; i < maxPopSize; i++)
{
researchers [i].Init (coords, journalsNum);
researchers [i].alive = (i < popSize);
if (i < popSize)
{
researchers [i].m = fundsPerResearcher;
researchers [i].s = u.RNDprobab ();
// Инициализация позиции
for (int c = 0; c < coords; c++)
{
researchers [i].x [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]);
researchers [i].x [c] = u.SeInDiSp (researchers [i].x [c], rangeMin [c], rangeMax [c], rangeStep [c]);
researchers [i].b [c] = researchers [i].x [c];
researchers [i].v [c] = u.GaussDistribution (0.0, -0.01, 0.01, 1);
}
// Инициализация вероятностей журналов
for (int j = 0; j < journalsNum; j++)
{
researchers [i].rho [j] = u.RNDprobab ();
}
NormalizeProbabilities (researchers [i].rho);
}
}
// Вычисляем начальное стандартное отклонение
sigma0 = ComputeStdDev ();
if (sigma0 == 0) sigma0 = 1.0; // Защита от деления на ноль
omegaCurrent = omegaMin + (omegaMax - omegaMin) / 2.0;
// Копируем исследователей в стандартный массив агентов
for (int i = 0; i < popSize; i++)
{
ArrayCopy (a [i].c, researchers [i].x, 0, 0, WHOLE_ARRAY);
}
return true;
}
//————————————————————————————————————————————————————————————————————
//————————————————————————————————————————————————————————————————————
void C_AO_CoSO::Moving ()
{
if (!revision)
{
revision = true;
return;
}
//--- Основные шаги CoSO:
// 1. Обновление fitness исследователей из массива агентов
int aSize = ArraySize (a);
for (int i = 0, j = 0; i < actualPopSize && j < aSize; i++)
{
if (!researchers [i].alive) continue;
researchers [i].f = a [j].f;
// Обновление личного лучшего
if (researchers [i].f > researchers [i].fb)
{
researchers [i].fb = researchers [i].f;
ArrayCopy (researchers [i].b, researchers [i].x, 0, 0, WHOLE_ARRAY);
}
j++;
}
// 2. Подача результатов в журналы
for (int i = 0; i < actualPopSize; i++)
{
if (researchers [i].alive) SubmitToJournal (i);
}
// 3. Сбор глобального отчета и подсчет доступных средств
int availableFunds = 0;
int reportSize = 0;
// Предварительный подсчет размера отчета
for (int i = 0; i < actualPopSize; i++)
{
if (!researchers [i].alive) continue;
researchers [i].m--; // Тратим 1 единицу средств за итерацию
availableFunds++;
if (researchers [i].m > 0) reportSize++;
else researchers [i].alive = false;
}
// Заполнение глобального отчета
ArrayResize (globalReport, reportSize);
int idx = 0;
for (int i = 0; i < actualPopSize && idx < reportSize; i++)
{
if (researchers [i].alive && researchers [i].m > 0)
{
globalReport [idx].fitness = researchers [i].f;
globalReport [idx].index = i;
idx++;
}
}
// 4. Быстрая сортировка глобального отчета
for (int i = 0; i < reportSize - 1; i++)
{
for (int j = i + 1; j < reportSize; j++)
{
if (globalReport [i].fitness < globalReport [j].fitness)
{
S_GlobalReport temp;
temp = globalReport [i];
globalReport [i] = globalReport [j];
globalReport [j] = temp;
}
}
}
// 5. Распределение средств
AssignFunds (availableFunds);
// 6. Найм новых исследователей существующими
for (int i = 0; i < actualPopSize; i++)
{
if (researchers [i].alive && researchers [i].m > 1) HireResearchers (i);
}
// 7. Обновление направления и позиции для каждого исследователя
for (int i = 0; i < actualPopSize; i++)
{
if (!researchers [i].alive) continue;
UpdateDirection (i);
// Обновление позиции
for (int c = 0; c < coords; c++)
{
researchers [i].x [c] += researchers [i].v [c];
// Контроль границ
if (researchers [i].x [c] < rangeMin [c]) researchers [i].x [c] = rangeMin [c];
if (researchers [i].x [c] > rangeMax [c]) researchers [i].x [c] = rangeMax [c];
researchers [i].x [c] = u.SeInDiSp (researchers [i].x [c], rangeMin [c], rangeMax [c], rangeStep [c]);
}
}
// 8. Обновление параметра разнообразия
UpdateOmega ();
// 9. Компактификация популяции
CompactPopulation ();
// 10. Копируем позиции в массив агентов для вычисления fitness
ArrayResize (a, actualPopSize);
idx = 0;
for (int i = 0; i < maxPopSize && idx < actualPopSize; i++)
{
if (researchers [i].alive)
{
a [idx].Init (coords);
ArrayCopy (a [idx].c, researchers [i].x, 0, 0, WHOLE_ARRAY);
idx++;
}
}
popSize = actualPopSize; // Обновляем размер популяции
}
//————————————————————————————————————————————————————————————————————
//————————————————————————————————————————————————————————————————————
void C_AO_CoSO::UpdateDirection (int idx)
{
double beta1 = u.RNDprobab ();
double beta2 = u.RNDprobab ();
// Социальный компонент
ArrayInitialize (socialComponent, 0);
for (int j = 0; j < journalsNum; j++)
{
if (journals [j].length > 0)
{
int entryIdx = u.RNDminusOne (journals [j].length);
for (int c = 0; c < coords; c++)
{
socialComponent [c] += researchers [idx].rho [j] *
(journals [j].entries [entryIdx].decision [c] - researchers [idx].x [c]);
}
}
}
// Обновление направления
for (int c = 0; c < coords; c++)
{
researchers [idx].v [c] = omega * researchers [idx].v [c] +
phi1 * beta1 * (researchers [idx].b [c] - researchers [idx].x [c]) +
phi2 * beta2 * socialComponent [c];
}
}
//————————————————————————————————————————————————————————————————————
//————————————————————————————————————————————————————————————————————
void C_AO_CoSO::SubmitToJournal (int idx)
{
int journalIdx = SelectJournal (researchers [idx].rho);
journals [journalIdx].Add (researchers [idx].f, researchers [idx].x);
}
//————————————————————————————————————————————————————————————————————
//————————————————————————————————————————————————————————————————————
int C_AO_CoSO::SelectJournal (const double &probs [])
{
double rnd = u.RNDprobab ();
double cumSum = 0;
int probsSize = ArraySize (probs);
for (int i = 0; i < probsSize; i++)
{
cumSum += probs [i];
if (rnd <= cumSum) return i;
}
return probsSize - 1;
}
//————————————————————————————————————————————————————————————————————
//————————————————————————————————————————————————————————————————————
void C_AO_CoSO::AssignFunds (int availableFunds)
{
// Средства для аутсайдеров
int outsiderFunds = (int)(availableFunds * omegaCurrent);
int existingFunds = availableFunds - outsiderFunds;
int reportSize = ArraySize (globalReport);
// Распределение средств существующим исследователям
if (reportSize > 0)
{
int totalRank = reportSize * (reportSize + 1) / 2;
for (int f = 0; f < existingFunds; f++)
{
double rnd = u.RNDprobab () * totalRank;
double cumSum = 0;
for (int i = 0; i < reportSize; i++)
{
cumSum += reportSize - i;
if (rnd <= cumSum)
{
researchers [globalReport [i].index].m++;
break;
}
}
}
}
// Создание аутсайдеров
CreateOutsiders (outsiderFunds);
}
//————————————————————————————————————————————————————————————————————
//————————————————————————————————————————————————————————————————————
void C_AO_CoSO::CreateOutsiders (int outsiderFunds)
{
if (outsiderFunds <= 0) return;
// Ограничиваем количество новых аутсайдеров, особенно если популяция уже большая
int maxNew = (actualPopSize > 100) ? 2 : 5;
int newResearchers = (int)(u.RNDfromCI (1, MathMin (outsiderFunds, maxNew)));
int fundsPerNew = outsiderFunds / newResearchers;
for (int i = 0; i < newResearchers; i++)
{
// Находим свободное место
int idx = -1;
for (int j = 0; j < actualPopSize; j++)
{
if (!researchers [j].alive)
{
idx = j;
break;
}
}
if (idx == -1 && actualPopSize < maxPopSize)
{
idx = actualPopSize;
}
if (idx == -1) // Нет места, расширяем массив
{
if (actualPopSize >= maxPopSize)
{
// Ограничиваем рост популяции
int newMaxSize = MathMin (maxPopSize + 50, 500);
if (newMaxSize == maxPopSize) continue; // Достигнут лимит, пропускаем создание
maxPopSize = newMaxSize;
ArrayResize (researchers, maxPopSize);
for (int j = actualPopSize; j < maxPopSize; j++)
{
researchers [j].Init (coords, journalsNum);
researchers [j].alive = false;
}
idx = actualPopSize;
}
}
if (idx == -1) continue; // Не удалось создать
researchers [idx].alive = true;
researchers [idx].m = fundsPerNew;
researchers [idx].s = u.RNDprobab ();
// Случайная инициализация
for (int c = 0; c < coords; c++)
{
researchers [idx].x [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]);
researchers [idx].x [c] = u.SeInDiSp (researchers [idx].x [c], rangeMin [c], rangeMax [c], rangeStep [c]);
researchers [idx].b [c] = researchers [idx].x [c];
researchers [idx].v [c] = u.GaussDistribution (0.0, -0.01, 0.01, 1);
}
// Инициализация вероятностей журналов
for (int j = 0; j < journalsNum; j++)
{
researchers [idx].rho [j] = u.RNDprobab ();
}
NormalizeProbabilities (researchers [idx].rho);
if (idx >= actualPopSize) actualPopSize = idx + 1;
}
}
//————————————————————————————————————————————————————————————————————
//————————————————————————————————————————————————————————————————————
void C_AO_CoSO::HireResearchers (int idx)
{
if (researchers [idx].m <= 1) return;
int keepFunds = (int)(researchers [idx].m * researchers [idx].s);
int hireFunds = researchers [idx].m - keepFunds;
researchers [idx].m = keepFunds;
if (hireFunds <= 0) return;
// Ограничиваем количество нанимаемых, особенно при большой популяции
int maxNew = (actualPopSize > 100) ? 1 : 3;
int newCount = (int)(u.RNDfromCI (1, MathMin (hireFunds, maxNew)));
int fundsPerNew = hireFunds / newCount;
for (int i = 0; i < newCount; i++)
{
// Находим свободное место
int newIdx = -1;
for (int j = 0; j < actualPopSize; j++)
{
if (!researchers [j].alive)
{
newIdx = j;
break;
}
}
if (newIdx == -1 && actualPopSize < maxPopSize)
{
newIdx = actualPopSize;
}
if (newIdx == -1) // Нет места
{
if (actualPopSize >= maxPopSize || actualPopSize >= 500) continue; // Пропускаем создание при достижении лимита
}
if (newIdx == -1) continue; // Не удалось найти место
researchers [newIdx].alive = true;
researchers [newIdx].m = fundsPerNew;
researchers [newIdx].s = u.GaussDistribution (researchers [idx].s, 0, 1, 1);
if (researchers [newIdx].s < 0) researchers [newIdx].s = 0;
if (researchers [newIdx].s > 1) researchers [newIdx].s = 1;
// Наследование от супервизора
ArrayCopy (researchers [newIdx].b, researchers [idx].b, 0, 0, WHOLE_ARRAY);
// Позиция около супервизора
for (int c = 0; c < coords; c++)
{
researchers [newIdx].x [c] = researchers [idx].x [c] + u.GaussDistribution (0.0, -0.01, 0.01, 1);
// Контроль границ
if (researchers [newIdx].x [c] < rangeMin [c]) researchers [newIdx].x [c] = rangeMin [c];
if (researchers [newIdx].x [c] > rangeMax [c]) researchers [newIdx].x [c] = rangeMax [c];
researchers [newIdx].x [c] = u.SeInDiSp (researchers [newIdx].x [c], rangeMin [c], rangeMax [c], rangeStep [c]);
researchers [newIdx].v [c] = u.GaussDistribution (0.0, -0.01, 0.01, 1);
}
// Возмущенные вероятности журналов
for (int j = 0; j < journalsNum; j++)
{
researchers [newIdx].rho [j] = u.GaussDistribution (researchers [idx].rho [j], 0, 1, 1);
if (researchers [newIdx].rho [j] < 0) researchers [newIdx].rho [j] = 0;
}
NormalizeProbabilities (researchers [newIdx].rho);
if (newIdx >= actualPopSize) actualPopSize = newIdx + 1;
}
}
//————————————————————————————————————————————————————————————————————
//————————————————————————————————————————————————————————————————————
double C_AO_CoSO::ComputeStdDev ()
{
if (actualPopSize == 0) return 0;
double mean = 0;
int count = 0;
for (int i = 0; i < actualPopSize; i++)
{
if (researchers [i].alive)
{
mean += researchers [i].f;
count++;
}
}
if (count == 0) return 0;
mean /= count;
double variance = 0;
for (int i = 0; i < actualPopSize; i++)
{
if (researchers [i].alive)
{
variance += MathPow (researchers [i].f - mean, 2);
}
}
variance /= count;
return MathSqrt (variance);
}
//————————————————————————————————————————————————————————————————————
//————————————————————————————————————————————————————————————————————
void C_AO_CoSO::UpdateOmega ()
{
double currentSigma = ComputeStdDev ();
if (currentSigma < sigma0)
{
// Увеличиваем долю аутсайдеров при сходимости
omegaCurrent += (omegaMax - omegaMin) / 2.0 * epsilonPlus;
}
else
{
// Уменьшаем долю аутсайдеров
omegaCurrent -= (omegaMax - omegaMin) / 2.0 * epsilonMinus;
}
// Ограничиваем диапазон
if (omegaCurrent < omegaMin) omegaCurrent = omegaMin;
if (omegaCurrent > omegaMax) omegaCurrent = omegaMax;
}
//————————————————————————————————————————————————————————————————————
//————————————————————————————————————————————————————————————————————
void C_AO_CoSO::NormalizeProbabilities (double &probs [])
{
double sum = 0;
int size = ArraySize (probs);
for (int i = 0; i < size; i++)
{
sum += probs [i];
}
if (sum > 0)
{
for (int i = 0; i < size; i++)
{
probs [i] /= sum;
}
}
else
{
// Равномерное распределение
double val = 1.0 / size;
for (int i = 0; i < size; i++)
{
probs [i] = val;
}
}
}
//————————————————————————————————————————————————————————————————————
//————————————————————————————————————————————————————————————————————
void C_AO_CoSO::CompactPopulation ()
{
// Подсчитываем живых исследователей
int aliveCount = 0;
for (int i = 0; i < actualPopSize; i++)
{
if (researchers [i].alive) aliveCount++;
}
// Если слишком много мертвых, компактифицируем
if (aliveCount < actualPopSize * 0.75 || actualPopSize > 200)
{
int newIdx = 0;
for (int i = 0; i < actualPopSize; i++)
{
if (researchers [i].alive)
{
if (i != newIdx)
{
// Копируем живого исследователя на новое место
researchers [newIdx] = researchers [i];
researchers [i].alive = false;
}
newIdx++;
}
}
actualPopSize = aliveCount;
// Если популяция все еще слишком большая, ограничиваем
if (actualPopSize > 150)
{
// Сортируем по fitness и оставляем лучших
for (int i = 0; i < actualPopSize - 1; i++)
{
for (int j = i + 1; j < actualPopSize; j++)
{
if (researchers [i].f < researchers [j].f)
{
S_Researcher temp;
temp = researchers [i];
researchers [i] = researchers [j];
researchers [j] = temp;
}
}
}
// Убиваем худших
for (int i = 150; i < actualPopSize; i++)
{
researchers [i].alive = false;
researchers [i].m = 0;
}
actualPopSize = 150;
}
}
}
//————————————————————————————————————————————————————————————————————
//————————————————————————————————————————————————————————————————————
void C_AO_CoSO::Revision ()
{
int bestIND = -1;
int aSize = ArraySize (a);
for (int i = 0; i < aSize; i++)
{
if (a [i].f > fB)
{
fB = a [i].f;
bestIND = i;
}
}
if (bestIND != -1)
{
ArrayCopy (cB, a [bestIND].c, 0, 0, WHOLE_ARRAY);
}
}
//————————————————————————————————————————————————————————————————————