//+——————————————————————————————————————————————————————————————————+ //| 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); } } //————————————————————————————————————————————————————————————————————