Русский Português
preview
Community of Scientists Optimization (CoSO): Theory

Community of Scientists Optimization (CoSO): Theory

MetaTrader 5Trading |
103 0
Andrey Dik
Andrey Dik

Contents

  1. Introduction
  2. Implementation of the algorithm
  3. Conclusion


Introduction

Continuing our exploration of optimization methods, in this article we will consider another approach to solving optimization problems - the CoSO (Community of Scientists Optimization) algorithm, based on simulating the mechanisms of the scientific community. Unlike classical bio-inspired algorithms, CoSO replicates the unique characteristics of scientific activity: publishing results in journals, competition for grants, forming research groups, and balancing in-depth study of known areas with the search for fundamentally new solutions. The CoSO algorithm was developed and published in 2012 by two scientists, A. Milani and V. Santucci.

An interesting feature of this approach is the natural self-organization of the search process. Like a real scientific community, the algorithm concentrates resources in promising areas, stores and disseminates the best solutions through the journal mechanism, and maintains the necessary diversity by funding "outsiders". Dynamically changing population size allows the algorithm to adapt to the specifics of a particular problem without the need for fine-tuning of parameters, which is another innovation because we usually use a constant population. In this article, we will take a detailed look at the mathematical foundations of the algorithm, its key components, and the mechanisms of interaction between them. 


Implementation of the algorithm

The CoSO population algorithm is based on the idea of simulating the behavior of researchers who search for optimal solutions in a multidimensional space, exchange knowledge through publications in journals, and compete for limited resources (grants). Each researcher is a point in the search space. It has: a position (where it is searching), a direction of movement and funds for research. If the money runs out, the researcher becomes inactive.

Each researcher is represented by a set of parameters: a position in the search space (x), movement direction (v), personal best result (b), amount of funds (m), fund management strategy (s) and the probabilities of choosing different journals (ρ).

The movement of researchers is determined by the formula for updating the direction, which combines three components: inertia (the tendency to continue moving in the same direction), a cognitive component (the desire for personal best results), and a social component (the influence of the best results from journals): 

v {i, t} = ω · v {i, t-1} + φ₁ · β₁ · (b {i, t-1} - x {i, t-1}) + φ₂ · β₂ · Σⱼ [ρᵢⱼ · (J {j,c} - x {i, t-1})], 

where ω = 0.7298 — inertia coefficient, φ₁ = φ₂ = 1.49618 — acceleration coefficients, β₁ and β₂ — random numbers from [0,1], ρᵢⱼ — probability of selecting journal j by researcher i, J {j,c} — random article from journal j.

After updating the direction, the researcher's position changes according to a simple formula: x {i, t} = x {i, t-1} + v {i, t}

The journals store the best solutions found. Researchers read journals and move towards good solutions, and only the best results make it into the journal. The journals in the algorithm serve as a collective memory, storing the k best solutions found by all researchers. When a researcher obtains a new result, they submit it to one of the journals according to their probability distribution ρ, which accepts the result only if it is better than the worst of those already published. This ensures continuous improvement in the quality of information available to the community.

Successful researchers receive more funding. Part of the money goes to "outsiders" - new researchers in random places. This helps to avoid getting stuck in local optima. The mechanism for distributing funds imitates competitive funding in science. At each iteration, researchers spend one unit of funds, and the total amount of money spent is redistributed based on rank roulette selection. The probability of receiving funds for a researcher at position i in the global ranking is determined by the formula:

P (i) = (N - i + 1) / (N · (N + 1) / 2),

where N is the number of researchers.

Part of the Ω funds is reserved for the creation of "outsiders" - new researchers in random positions. The Ω parameter is adaptively changed to maintain a balance between exploration and exploitation. When the standard deviation of the population σ fitness values becomes less than the initial σ₀, this signals convergence, and the proportion of outsiders increases:

Ω {t} = Ω {t-1} + (Ω {max} - Ω {min}) / 2 · ε⁺ for σ < σ₀, otherwise Ω {t} = Ω {t-1} - (Ω {max} - Ω {min}) / 2 · ε⁻,

where Ω {min} = 0.2, Ω {max} = 0.5, ε⁺ = 0.2, ε⁻ = 0.1.

Successful scientists can hire assistants who begin their search near their supervisor. This helps to explore promising areas in detail. Successful researchers with funds m > 1 can hire new researchers. In this case, the researcher keeps a share of the funds for themselves, according to their s strategy, and spends the rest on hiring.

New researchers inherit the supervisor's characteristics with minor perturbations: the position is initialized as x {new} = x {supervisor} + N (0, σv), strategy is initialized as s {new} = N (s {supervisor}, σs), journal probabilities as ρ {new, j} = N (ρ {supervisor, j},  σρ) with subsequent normalization.

The search process in the algorithm can be compared to a group of people picking mushrooms in the forest: everyone searches in their own place, and when someone finds a mushroom spot, they shout to the others, then part of the group goes to that spot, and the rest continues to search for new places. Those who find nothing for a long time go home, but the most successful mushroom pickers bring friends to mushroom spots.

The algorithm's performance is driven by a combination of mechanisms: journals facilitate information sharing and direct searches toward promising areas, competitive funding creates selection pressure by removing ineffective researchers, hiring assistants ensures local searches around good solutions, and outsiders prevent premature convergence.

Dynamically changing population size allows the algorithm to adapt to the complexity of the problem, increasing the number of researchers in promising areas and reducing it in unpromising ones. All these mechanisms work together to create a self-organizing system capable of solving problems in complex multidimensional spaces. 

coso

Figure 1. CoSO algorithm in operation 

The image shows the following:

  1. The Search Space where researchers (green circles) move towards the optimum, their motion vectors are shown.
  2. Scientific Journals store the best solutions found, researchers read them and publish their results.
  3. Funds Distribution - a financing mechanism in multidimensional spaces with the Ω adaptive parameter for outsiders.
  4. The formula for updating the direction - the mathematical basis for the movement of researchers.
  5. The key mechanisms - a legend with researcher types: active (with funds), inactive (without funds), new hires, and outsiders with random positions.

The arrows indicate the flows of information (grey), movement (blue) and funds (orange). Let us now outline the algorithm in pseudocode.

1. INITIALIZATION:
- Create 10 researchers in random positions
- Give everyone 15 units of funds (150 / 10)
- Everyone gets:
     * Random savings strategy (from 0 to 1)
     * Random journal preferences
     * Initial movement direction (small random)
- Create 3 empty journals
- Remember the initial population spread

2. MAIN CYCLE (repeat specified number of times):
   
   2.1. EVALUATION OF RESULTS:
        For every living researcher:
- Calculate the quality of the current position f(x)
- If the result is better than a personal record:
* Update personal record

   2.2. PUBLICATION IN JOURNALS:
For every living researcher:
- Choose a journal according to your personal preferences
- Attempt to publish the result:
* The journal accepts if the result is in the top 10.
* The worst article is eliminated

   2.3. COST ACCOUNTING AND REPORTING:
- Collect all spent funds (1 from each)
- Compile a ranking of living researchers
- Mark as "dead" those who have run out of money

   2.4. FUNDS DISTRIBUTION:
        - Determine the share for outsiders (20-50%)
        - Distribute the rest between the existing ones:
          * The higher the ranking, the greater the chances
* Use weighted roulette-wheel selection

   2.5. CREATING OUTSIDERS:
- Spend the allocated funds on 1-5 newcomers
- Place them at random positions
- Give equal shares of the allocated budget

   2.6. HIRING ASSISTANTS:
        For each wealthy researcher (funds > 1):
- Keep a part for oneself according to the strategy
- Hire 1-3 assistants for the rest:
* Place next to oneself
* Share your experience with minor changes

   2.7. MOVEMENT DIRECTION SELECTION:
For each living researcher, calculate:
        
        New_direction = 
        0.7 × Old_direction +                                    // Inertia
        1.5 × Randomness × (Personal_record - Position) +  // To one's best
        1.5 × Randomness × Sum_by_journals               // To the best of the journals
        (Journal_weight × (Journal_article - Position))

   2.8. MOVEMENT:
        For every living researcher:
- Take a step: New_position = Old_position + Direction
- If going outside the boundaries, move it back

   2.9. DIVERSITY CONTROL:
- Measure the current population spread
- If the spread has decreased (converging):
          * Increase the share of outsiders by 10%
        - Otherwise:
* Reduce the share of outsiders by 5%

   2.10. POPULATION SIZE MANAGEMENT:
- If there are more than 25% dead or more than 200 in total:
           * Remove dead from array
         - If there are more than 150 left:
           * Keep only the top 150

3. RESULT:
   - Return the best solution found

Now we can move on to the practical implementation of the CoSO algorithm. Let's write three data structures. The first structure, S_Journal_Entry, represents a single journal entry that contains:

  • fitness — a numeric value that reflects the "quality" or "fitness" of a given entry. 
  • decision[] - dynamic array storing the parameters associated with this entry.
The Init method is used for initialization by setting the size of the decision array and assigning fitness an initial minimum value. The second structure, S_Journal, represents the journal itself - an ordered collection of entries characterized by:
  • entries [] — dynamic array of S_Journal_Entry entries.
  • length — current number of entries in the journal.
  • maxLength — maximum number of records that the journal can store.

The Init method initializes the journal by setting the maximum length, allocating memory for entries and initializing each entry with a minimum fitness value. The Add method is used to add a new entry to the journal. The peculiarity of this method is that it maintains the journal in sorted order by fitness value. If a new entry is worse than the worst entry in an already filled journal, it is not added. Otherwise, space for the new entry is found using binary search, then existing entries are shifted out to make room, and the new entry is inserted; if the journal is not already full, its "length" is increased.

The third structure, S_Researcher, describes the "researcher" or "agent". It contains a set of parameters:

  • x [] — current position of the researcher (array of coordinates)
  • v [] — direction or velocity vector of the researcher's movement
  • b [] — the best position found by the given researcher individually
  • rho [] — array of publication probabilities in different journals. The researcher interacts with several "journals" or sources of information
  • s — funds management strategy
  • m — amount of funds (integer)
  • f — fitness of the current researcher position
  • fb — fitness of the best position found by the researcher
  • alive — flag indicating the researcher's activity
The Init method initializes the researcher by setting the sizes of the positions, speeds, best positions, and publication probabilities arrays, and assigns initial values to the remaining parameters. The presented data structures are components for implementing a method, in which "researchers" search for optimal solutions using "journals" to store and share the best solutions found. 
//————————————————————————————————————————————————————————————————————
// Structure for the journal
struct S_Journal_Entry
{
    double fitness;
    double decision [];

    void Init (int coords)
    {
      ArrayResize (decision, coords);
      fitness = -DBL_MAX;
    }
};

// Journal structure
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; // Initialize with the minimum value
      }
    }

    void Add (double fit, const double &coord [])
    {
      // Quick check - if it is the worst and the journal is full, do not add 
      if (length >= maxLength && fit <= entries [length - 1].fitness) return;

      int insertPos = length;

      // Find the insertion position (binary search)
      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 we insert at the end and the journal is full
      if (insertPos >= maxLength) return;

      // Shift the elements
      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);
      }

      // Insert a new element
      entries [insertPos].fitness = fit;
      ArrayCopy (entries [insertPos].decision, coord, 0, 0, WHOLE_ARRAY);
    }
};

// Extended researcher structure
struct S_Researcher
{
    double x   [];   // current position
    double v   [];   // movement direction
    double b   [];   // personal best result
    double rho [];   // probabilities of publication in journals
    double s;        // funds management strategy
    int    m;        // amount of funds
    double f;        // fitness of the current position
    double fb;       // fitness of the best position
    bool   alive;    // activity flag

    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;
    }
};

Let's write the C_AO_CoSO class, which is an implementation of the "Community of Scientists Optimization" (CoSO) optimization algorithm. The C_AO_CoSO class inherits from the C_AO base class and assumes the presence of common properties and methods for various optimization algorithms.

Default values for various CoSO control parameters are set:

  1. initial size of the "researcher" population;
  2. total amount of "funds" (resources) that can be allocated (in the scientists' metaphor, this could be grants that they use for their "work" (searching for a solution));
  3. number of "journals", in which researchers can publish their "discoveries" (best solutions);
  4. maximum number of articles/discoveries that one "journal" can contain;
  5. "inertia" parameter, which controls the influence of the previous movement direction.

  • phi1 = 1.5 — cognitive parameter reflects the individual influence on the researcher’s movement (the desire for one’s own best discoveries);
  • phi2 = 1.5 — social parameter determines how strongly the researcher focuses on publications in journals;
  • omegaMin = 0.2 — minimum percentage of "outsiders";
  • omegaMax = 0.5 — maximum percentage of "outsiders";
  • epsilonPlus = 0.2 — diversity increase step;
  • epsilonMinus = 0.1 — diversity reduction step.
SetParams () — method for setting internal class parameters based on the external "params" array. 
Init () — algorithm initialization method. It accepts search ranges (min, max values) and steps for each parameter, as well as the number of epochsP.
Moving () — method is responsible for "moving" (updating the positions) of researchers in the search space, based on their "speeds" and "directions".
Revision () — method is responsible for updating the best global solution found by all researchers up to the current moment.

Private fields: dynamic array of S_Researcher structures. Each element of this structure represents one "researcher" and contains information about its current position, speed, best solution found, "alive", "fitness" (f), etc. The dynamic array of S_Journal structures, where each structure stores a set of the best solutions published by researchers in a given "journal". Current percentage of "outsiders". Initial standard deviation. Current actual population size. It can change during the execution of the algorithm. Maximum permissible population size. 

struct S_GlobalReport — a nested structure for storing information about the best global solution: "fitness" (the value of the objective function) and "index" (the index of the researcher who found this solution).

S_GlobalReport globalReport [] — array for storing global reports. 

Private algorithm methods:

  • UpdateDirection () — update the movement direction (or "velocity") for a specific researcher with the idx index.
  • SubmitToJournal () — allow a researcher with the index to "publish" their current best solution in one of the "journals".
  • AssignFunds () — method for distributing "funds" among researchers. In CoSO, grants can influence the behavior or ability of researchers to search.
  • HireResearchers () — method creates new researchers.
  • CreateOutsiders () — create "outsiders", special researchers that can conduct more aggressive or random searches to avoid getting stuck in local optima.
  • ComputeStdDev () — calculates the standard deviation of a population, serves as an indicator of the "diversity" or "convergence" of a population.
  • UpdateOmega () — update the current omegaCurrent parameter.
  • SelectJournal () — select a journal for publication based on given probabilities.
  • NormalizeProbabilities () — normalize the array of probabilities so that their sum is equal to one.
  • CompactPopulation () — method for "compacting" a population, removing the least effective researchers, i.e., managing the population size.
//————————————————————————————————————————————————————————————————————
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/en/articles/18886";

    popSize      = 30;     // initial population size
    totalFunds   = 150;    // total amount of funds
    journalsNum  = 3;      // number of journals
    journalLen   = 10;     // journal length
    omega        = 0.7;    // inertia parameter

    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;    // cognitive parameter
    phi2         = 1.5;    // social parameter
    omegaMin     = 0.2;    // minimum percentage of outsiders
    omegaMax     = 0.5;    // maximum percentage of outsiders
    epsilonPlus  = 0.2;    // diversity increase step 
    epsilonMinus = 0.1;    // diversity reduction step
  }

  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;   // total amount of funds
  int    journalsNum;  // number of journals
  int    journalLen;   // journal length
  double omega;        // inertia parameter
  double phi1;         // cognitive parameter
  double phi2;         // social parameter
  double omegaMin;     // minimum percentage of outsiders
  double omegaMax;     // maximum percentage of outsiders
  double epsilonPlus;  // diversity increase step
  double epsilonMinus; // diversity reduction step

  private: //---------------------------------------------------------
  S_Researcher researchers [];  // array of researchers
  S_Journal    journals    [];  // array of journals
  double       omegaCurrent;    // current percentage of outsiders
  double       sigma0;          // initial standard deviation
  int          actualPopSize;   // current population size
  int          maxPopSize;      // maximum population size
  double       socialComponent []; // cache for the social component

  struct S_GlobalReport
  {
      double fitness;
      int    index;
  };

  S_GlobalReport globalReport [];

  // Algorithm methods
  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 ();
};
//————————————————————————————————————————————————————————————————————

The Init method is part of the C_AO_CoSO class and is responsible for preparing all the necessary structures and parameters before the algorithm starts running. First, a standard initialization occurs, related to the ranges of search parameters (minimum, maximum values and step), with the number of epochs (epochsP) set. If this standard initialization fails, the function returns 'false'. Then the internal state variables of the algorithm are initialized:

  • actualPopSize — current size of the research population,
  • maxPopSize — maximum allowable population size (or the allocated memory for researchers),
  • sigma0 - initial value of the standard deviation, which will be calculated later,
  • omegaCurrent — current "omega" parameter, initialized as the arithmetic mean between omegaMin and omegaMax.

Next comes the check of the input parameters validity:

  • totalFunds - the total amount of "funds" should be no less than the population size, each researcher should be allocated a certain amount of funds or grants;
  • journalsNum — the number of "journals" should be at least one;
  • journalLen — the length of each "journal" should also be at least one;
  • omega (global parameter) — should be in the range from 0.0 to 1.0.

After checking the parameters, a "full clear from previous runs" occurs, which means resetting the sizes of the dynamic "researchers", "journals", "globalReport" and "socialComponent" arrays to zero; the algorithm always starts from a clean state. The already mentioned state variables (actualPopSize, maxPopSize, sigma0) are reset again.

Then the "journals" are initialized: the "journals" array is resized according to journalsNum. Each journal in this array is initialized with a given journalLen length and a given number of 'coords' coordinates.

Initialization of researchers. maxPopSize is set to at least three times popSize (the population size). This creates a "reserve" for possible changes in population size (the algorithm creates new researchers). fundsPerResearcher is calculated - the amount of funds that will be allocated to each researcher (the total amount of funds is divided by the population size). Next comes a cycle through all the researchers.

Each researcher is initialized with a number of 'coords' coordinates and journalsNum number of journals. The "alive" flag is set to 'true' only for the first popSize researchers, the rest are still inactive. Active researchers are assigned the calculated fundsPerResearcher amount of funds. Their 's' "fund management strategy" is initialized randomly. Their positions 'x', best personal positions 'b' and motion vectors 'v' are initialized. The positions of 'x' and 'b' are set randomly in the given range (rangeMin - rangeMax, taking into account the rangeStep). The 'v' motion vectors are initialized to a small random value from a normal distribution. The 'rho' probabilities for each journal are initialized to random values.

The NormalizeProbabilities function is called, which normalizes these probabilities. After initialization of all researchers, the sigma0 initial standard deviation is calculated, which characterizes the spread of researchers' positions across all coordinates. If sigma0 happens to be zero (for example, if all the researchers are at the same point), it is set to 1.0 to prevent division by 0. The omegaCurrent parameter is again set to the average between omegaMin and omegaMax.

At the end, the positions of all initialized researchers (popSize) are copied into the "standard agent array" of 'a'. Thus, the Init function completely prepares the environment for the operation of the optimization algorithm, initializing all "researcher" agents, their parameters, "journals" for storing results, as well as internal parameters for controlling the algorithm.

//————————————————————————————————————————————————————————————————————
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;

  //------------------------------------------------------------------
  // Initialize state variables
  actualPopSize = 0;
  maxPopSize    = 0;
  sigma0        = 0;
  omegaCurrent  = omegaMin + (omegaMax - omegaMin) / 2.0;

  // Check the parameters validity
  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;

  // Complete reset of data from previous runs
  ArrayResize (researchers,     0);
  ArrayResize (journals,        0);
  ArrayResize (globalReport,    0);
  ArrayResize (socialComponent, 0);

  // Reset parameters
  actualPopSize = 0;
  maxPopSize    = 0;
  sigma0        = 0;

  // Initialize the journals
  ArrayResize (journals, journalsNum);
  for (int i = 0; i < journalsNum; i++)
  {
    journals [i].Init (journalLen, coords);
  }

  // Initialize the array of researchers with a reserve
  maxPopSize = MathMin (popSize * 3, 300); // Limit the maximum size
  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 ();

      // Initialize a position
      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);
      }

      // Initialize journal probabilities
      for (int j = 0; j < journalsNum; j++)
      {
        researchers [i].rho [j] = u.RNDprobab ();
      }

      NormalizeProbabilities (researchers [i].rho);
    }
  }

  // Calculate the initial standard deviation
  sigma0 = ComputeStdDev ();

  if (sigma0 == 0) sigma0 = 1.0; // Protection against zero divide
  omegaCurrent = omegaMin + (omegaMax - omegaMin) / 2.0;

  // Copy the researchers to the standard array of agents
  for (int i = 0; i < popSize; i++)
  {
    ArrayCopy (a [i].c, researchers [i].x, 0, 0, WHOLE_ARRAY);
  }

  return true;
}
//————————————————————————————————————————————————————————————————————

The Moving method is the central part of the iterative process of the algorithm, performing one search step toward an optimal solution, simulating the behavior of the "researchers" and their interaction. At the start of the function, if this is the first call after initialization (revision = false), it simply sets "revision" to 'true' and exits. The following are the main steps of the algorithm that are performed at each iteration.

Researcher fitness update. For each active researcher (from the current population), its fitness value is taken from the general 'a' array, which stores the results of the fitness assessment of all agents. It is then checked whether the researcher's current fitness is better than their previously recorded best personal fitness, and if so, fb is updated and the researcher's best position b is also stored as their current position of x.

Submitting results to journals. Each active researcher "submits" their current results (current fitness and position) to one of the "journals". The details of this process are encapsulated in the SubmitToJournal function, which selects a journal based on the researcher's 'rho' probabilities and adds their result there if it is good enough.

Collecting a global report and calculating available funds. At this step, the status of researchers is re-evaluated and resources are prepared for distribution. For each living researcher, the number of their "funds" (m) decreases by one, symbolizing the costs of the current iteration. The total amount of availableFunds is increasing. If the researcher has funds remaining after spending the funds (m > 0), they are considered able to continue working and will be included in the "global report". If the funds run out, the researcher "dies" (alive = false). A globalReport is generated - an array containing the fitness and index of only those researchers who remain active and have funds.

Sorting the global report. The items in globalReport are sorted by descending "fitness" value (from best to worst) using a simple bubble sort algorithm. This allows the most successful researchers to be quickly identified.

Funds distribution. The AssignFunds function is called to redistribute availableFunds among active researchers, with the best researchers (as determined by globalReport) receiving more funds, incentivizing their continued activity, while less successful researchers may receive less or nothing, potentially leading to their "death" in future iterations.

Hiring new researchers by existing ones. Current researchers who have sufficient funds remaining (m > 1) can "hire" new researchers. This is a mechanism for increasing the population or creating new agents based on successful existing ones. Details in the HireResearchers method.

Updating direction and position. For each active researcher, the UpdateDirection function is called, which calculates the researcher's new 'v' movement vector based on their personal best result of 'b', the best result from the journals, the social component, or other algorithm factors. The researcher's position 'x' is updated by adding the motion vector of 'v'. The position is checked for out-of-range errors and adjusted if necessary, and is also "discretized" or "rounded" to the nearest acceptable rangeStep.

Update diversity parameter. The UpdateOmega function is called, which adjusts the omegaCurrent parameter of the algorithm. This parameter is used to control the balance between exploration (finding new areas) and exploitation (refining the solutions found), changing it dynamically during the algorithm's run to maintain population diversity.

Population compaction. The CompactPopulation function is called to remove inactive (alive = false) researchers from the "researchers" array, effectively reducing its size and freeing up memory.

Copying positions to the agents array. Finally, the current positions of all active researchers are copied into the 'a' array. The size of 'a' is updated according to actualPopSize. The global popSize variable is also updated to the new actualPopSize.

    Thus, the Moving function encapsulates the complete life cycle of an iteration of the CoSO algorithm, including evaluation, social interaction ("journals"), resource management, dynamic population change, and agent movement in the search space.

    //————————————————————————————————————————————————————————————————————
    void C_AO_CoSO::Moving ()
    {
      if (!revision)
      {
        revision = true;
        return;
      }
    
      //--- CoSO basic steps:
    
      // 1. Updating 'fitness' of researchers from the agents array
      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;
    
        // Update the personal best
        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. Submit results to journals
      for (int i = 0; i < actualPopSize; i++)
      {
        if (researchers [i].alive) SubmitToJournal (i);
      }
    
      // 3. Collect a global report and calculate available funds
      int availableFunds = 0;
      int reportSize = 0;
    
      // Preliminary calculation of the report size
      for (int i = 0; i < actualPopSize; i++)
      {
        if (!researchers [i].alive) continue;
    
        researchers [i].m--;  // Spend 1 unit of funds per iteration
        availableFunds++;
    
        if (researchers [i].m > 0) reportSize++;
        else researchers [i].alive = false;
      }
    
      // Fill out the global report
      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. Quick sorting of a global report
      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 = globalReport [i];
            globalReport [i] = globalReport [j];
            globalReport [j] = temp;
          }
        }
      }
    
      // 5. Distribution of funds
      AssignFunds (availableFunds);
    
      // 6. Hire new researchers by existing ones
      for (int i = 0; i < actualPopSize; i++)
      {
        if (researchers [i].alive && researchers [i].m > 1) HireResearchers (i);
      }
    
      // 7. Update the direction and position for each researcher
      for (int i = 0; i < actualPopSize; i++)
      {
        if (!researchers [i].alive) continue;
    
        UpdateDirection (i);
    
        // Update position
        for (int c = 0; c < coords; c++)
        {
          researchers [i].x [c] += researchers [i].v [c];
    
          // Boundary control
          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. Update the diversity parameter
      UpdateOmega ();
    
      // 9. Population compactification
      CompactPopulation ();
    
      // 10. Copy positions to the array of agents to calculate '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;  // Update the population size
    }
    //————————————————————————————————————————————————————————————————————
    

    The UpdateDirection function is responsible for updating the motion vector of 'v' for an individual researcher in the CoSO algorithm. This process is key to the movement of agents (researchers) in the search space and allows them to explore new areas and use information obtained both personally and from other agents. The function takes the idx parameter as input. This is an index of the researcher for whom the direction needs to be updated.

    At the beginning of the function, two random variables between 0 and 1 are generated: beta1 used as the coefficient for the personal component and beta2 used as the coefficient for the social component. Next, we calculate a social component, while the socialComponent array is reset ensuring that it is clean before each new calculation.

    The loop then goes through all the "journals" available to the algorithm, and for each journal it checks whether the journal contains entries (length > 0), since an empty journal cannot provide information for the social component. If the journal is not empty, one entry (entryIdx) is randomly selected from it. 

    For each coordinate (dimension of the search space), the social component is updated. The contribution of a selected journal entry is calculated as the probability of a researcher selecting the journal (rho[j]) * (journal position - researcher's current position). These contributions are summed across all journals to form the overall impact of social information on researcher movement. After calculating the social component, the researcher's movement vector is updated, the new movement vector (v [c]) = inertial component + personal component + social component.

    Let's analyze each part:

    Inertial component: omega * researchers [idx]. v [c]. Here "omega" (the omegaCurrent parameter set in the Init function and updated in Moving) is the inertia coefficient. It determines how much the researcher will maintain their previous movement direction. A large value means that the researcher will continue to move in the same direction; a small value means that it will change direction more quickly under the influence of other factors.

    Personal component: phi1 * beta1 * (researchers [idx]. b [c] — researchers [idx]. x [c]), where phi1 is an acceleration factor associated with personal experience, and beta1 is a random factor that adds stochasticity.
    (researchers [idx]. b [c] — researchers [idx]. x [c]) is a vector pointing from the researcher's current position to their best previously found personal position (fb). This component pulls the researcher back to their own successful places.

    Social component:  phi2 * beta2 * socialComponent [c], where phi2 is the acceleration factor associated with social experience (information from others), beta2 is a random factor that adds stochasticity, and socialComponent[c] is the vector calculated earlier that takes into account the influence of randomly selected decisions from all journals. This component pulls the researcher in the direction of successful positions found by the team. Combining these three components allows the researcher to simultaneously:
    1. maintain some inertial movement;
    2. "remember" and return to their own best finds;
    3. study and follow the successful findings of other researchers.

    As a result, UpdateDirection calculates a new motion vector for the researcher, which will be used in the next step to actually update its position in the search space.

    //————————————————————————————————————————————————————————————————————
    void C_AO_CoSO::UpdateDirection (int idx)
    {
      double beta1 = u.RNDprobab ();
      double beta2 = u.RNDprobab ();
    
      // Social component
      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]);
          }
        }
      }
    
      // Update direction
      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];
      }
    }
    //————————————————————————————————————————————————————————————————————
    

    The SubmitToJournal function describes the process of an individual researcher "publishing" their current results in one of the available "journals". This is a key mechanism for information dissemination and social learning within the CoSO algorithm. The function takes the idx parameter - the index of the researcher who wants to submit their results to the journal. The function operation consists of two main steps:

    Selecting a journal. First, the SelectJournal auxiliary function is called. This function takes the researcher's 'rho' array as input. The 'rho' array (representing probabilities or preferences) determines how likely a given researcher is to submit their results to a particular journal. The SelectJournal function randomly selects one of the available journals based on these probabilities and returns its index (journalIdx).

    Adding an entry to a journal. . Once a journal is selected, the Add method is called on it. Two parameters are passed to the method:

    • researchers [idx]. f — current fitness value of the researcher. This is the "quality" or "success" of the solution it found.
    • researchers [idx]. x — current position (coordinates in the search space) of the researcher. This is the solution that produced that fitness value.

      The Add method in the selected journal (the journal by journalIdx index) is responsible for actually adding this information. Thus, the journal preserves collective knowledge or best practices discovered by various researchers.

      In general, SubmitToJournal simulates publishing scientific results, where researchers choose where to submit their work, and then that work becomes available for others to see. This allows the algorithm to accumulate and distribute successful solutions among the population of agents.

      //————————————————————————————————————————————————————————————————————
      void C_AO_CoSO::SubmitToJournal (int idx)
      {
        int journalIdx = SelectJournal (researchers [idx].rho);
        journals [journalIdx].Add (researchers [idx].f, researchers [idx].x);
      }
      //————————————————————————————————————————————————————————————————————
      

      The SelectJournal function is designed to select one journal from a set of available ones based on specified probabilities. This is an implementation of roulette wheel selection or proportional selection, where each journal has a certain "weight" or "preference". The function takes one parameter: probs[] - an array of floating-point numbers representing the probabilities or relative preferences for choosing each journal.

      The function works as follows: first, a random rnd number is generated, uniformly distributed between 0 (inclusive) and 1 (exclusive), this will be used to determine which journal will be selected. The cumSum variable is initialized to zero and will accumulate the sums of probabilities as it goes through the 'probs' array.

        Iteration over probabilities. The function starts to iterate over the elements of the 'probs' array in order, from first to last. In each iteration, the current 'probs' probability is added to cumSum. Thus, cumSum is the sum of the probabilities from the beginning of the array to the current position of 'i'. The generated rnd random number is compared with the current cumSum. If rnd is less than or equal to cumSum, it means that the random number "fell" into the interval associated with the current journal (i.e., the journal with index 'i'). In this case, the function immediately returns the index of 'i'.

        Handling an extreme case. If the loop terminates and no journal has been selected (which could theoretically happen due to extremely low precision of the calculations, if rnd is 1 and the sum of all 'probs' is exactly 1, or if the sum of 'probs' is slightly less than 1), the function returns the index of the last journal (probsSize - 1). This ensures that the journal is always selected, even in borderline or pathological cases.

          Essentially, this function creates a series of "slots" on the number line from 0 to 1, where the length of each slot is proportional to the probability of the corresponding journal. Then it throws a "dart" (rnd random number) and observes which slot it lands in. This ensures that journals with higher probabilities are selected more often.

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


          Conclusion

          We have looked in detail at the theory of the CoSO algorithm, as well as part of the implementation of the algorithm methods in code. The volume of material turned out to be significantly greater than expected at the beginning of the work, and I decided to split the article into two parts. The algorithm itself is extremely interesting and multifaceted, with deep, multi-layered logic that deserves careful consideration.

          In the second part, I will continue to describe the specific methods, as well as test the algorithm and present conclusions about its performance.

          Programs used in the article

          # Name Type Description
          1 #C_AO.mqh
          Include
          Parent class of population optimization algorithms
          2 #C_AO_enum.mqh
          Include
          Enumeration of population optimization algorithms
          3 TestFunctions.mqh
          Include
          Library of test functions
          4
          TestStandFunctions.mqh
          Include
          Test stand function library
          5
          Utilities.mqh
          Include
          Library of auxiliary functions
          6
          CalculationTestResults.mqh
          Include
          Script for calculating results in the comparison table
          7
          Testing AOs.mq5
          Script The unified test stand for all population optimization algorithms
          8
          Simple use of population optimization algorithms.mq5
          Script
          A simple example of using population optimization algorithms without visualization
          9
          Test_AO_CoSO.mq5
          Script CoSO test stand

          Translated from Russian by MetaQuotes Ltd.
          Original article: https://www.mql5.com/ru/articles/18886

          Attached files |
          CoSO.zip (257.36 KB)
          Features of Custom Indicators Creation Features of Custom Indicators Creation
          Creation of Custom Indicators in the MetaTrader trading system has a number of features.
          Shape of Price: An Introduction to TDA and Takens Embedding in MQL5 Shape of Price: An Introduction to TDA and Takens Embedding in MQL5
          The article presents a practical foundation for shape analysis of price series in MQL5. It implements Takens time‑delay embedding to build a phase‑space point cloud and computes the full pairwise distance matrix under selectable norms. The CTDAPointCloud and CTDADistance classes are provided with a demo script that embeds chart data and outputs results, preparing inputs for downstream topological tools.
          Features of Experts Advisors Features of Experts Advisors
          Creation of expert advisors in the MetaTrader trading system has a number of features.
          MQL5 Trading Tools (Part 37): Adding a Per-Object Property-Editing Ribbon to the Canvas Drawing Layer MQL5 Trading Tools (Part 37): Adding a Per-Object Property-Editing Ribbon to the Canvas Drawing Layer
          We add a descriptor-driven property stack and a floating ribbon that binds to the current selection on the drawing layer. The article covers the descriptor list for each tool, the engine get/set API with snapshot-and-restore live preview, and widget renderers for color, opacity, line width, line style, fonts, and level visibility. You get in-place, real-time editing of object appearance via a compact, draggable panel.