
算术优化算法(AOA):从AOA到SOA(简单优化算法)
内容
概述
算术优化算法(AOA)是一种基于简单算术运算(如加法、减法、乘法和除法)的原创方法。其核心在于利用这些基本的数学原理来为各种问题寻找最优解。AOA由包括Laith Abualigah在内的一组研究人员开发,并于2021年首次提出。该算法属于元启发式方法(高级算法)类别,旨在针对复杂优化问题(在这些问题上,基于准确性的方法可能无效或不可行)在合理时间内找到、生成并概率性地从多个启发式方法中选择出能提供高质量解的方法。
这种方法因其简单性且同时巧妙地运用了基础的算术运算符,从而引起了我的兴趣。这些基本数学运算与元启发式方法的结合产生了协同效应,使得能够解决复杂的优化问题。AOA中使用的元启发式方法包括几个关键原则:
1. 种群方法。AOA使用一个解种群,这使其能够覆盖更广泛的潜在解空间。这样有助于避免陷入局部最优解,并扩大了搜索范围。
2. 无序性和随机性。在搜索中引入随机性元素有助于算法避免陷入局部最优解,并提供对解空间的更全面探索,从而增加了找到全局最优解的概率。
3. 探索与利用之间的平衡。与许多其他元启发式算法一样,AOA力求在探索解空间的新区域和利用已知的有效解之间取得平衡。这是通过使用算术运算来更新解的位置来实现的。
因此,AOA是元启发式算法的一个典型的例子,它有效地利用了种群方法、随机性以及探索与利用之间的平衡原则来解决优化问题。然而,我们将在实现并测试该算法后,具体讨论其在复杂和多维空间中寻找最优解的效率。
算法实现
AOA的基本思想是利用算术运算符的分配行为来寻找最优解。该算法的特点是原理简单、参数数量不多且易于实现。该算法利用了数学中四种基本算术运算符的分布特性,即:乘法(Mε × ε)、除法(Dε ÷ ε)、减法(Sε − ε)和加法(Aε + ε)。在AOA中,初始种群在[U; L]范围内随机生成,其中目标函数的搜索空间的上限和下限分别用U和L表示,使用以下方程:
x = L + (U − L) × δ, 其中x表示种群解,δ是一个取值范围在[0, 1]内的随机变量。
在每次迭代中,探索与利用策略的选择,即选择两组运算符中的一组(除法;乘法)或(减法;加法),取决于MoA(数学优化器加速)函数的结果,该函数本质上是一个计算出的概率值,并随每次迭代而变化,其计算方程为:
MoA(t) = Min + t × (Max − Min) ÷ Maxt,其中MoA(t)是第t次迭代时的函数结果,t表示当前迭代次数,范围在1到最大迭代次数(Maxt)之间。MoA的最小可能值用Min表示,最大值用Max表示。这些是算法的外部参数。
所有四个算术运算符方程都使用MoP(数学优化器)因子,其计算如下:
MoP(t) = 1 − (t ÷ Maxt)^(1 ÷ θ),其中MoP(t)表示第t次迭代时MoP函数的值。θ是一个控制迭代过程中利用性能的关键参数。在原始工作中,算法的作者将其设置为5。
下图1显示了MoA和MoP随当前迭代变化的图形。这些图形表明,随着每次迭代,MoA线性增加,这意味着选择(减法;加法)运算符组的概率增加,而选择(除法;乘法)运算符组的概率相应降低。反过来,MoP比率非线性降低,从而减少了种群中智能体当前位置的下一个增量,这意味着在优化过程中决策的细化程度增加。
图例1. 紫色表示MoA概率图形,绿色表示MoP比率图形。
如果满足MoA概率,AOA中的研究或全局搜索 将使用基于除法(D)和乘法(M)运算符的搜索策略进行,该策略表述如下,其中以下运算符以相等的概率执行:
xi,j(t+1) = best(xj) ÷ (MoPr + 𝜖) × ((Uj − Lj) × 𝜇 + Lj),如果rand2 < 0.5;
否则:
xi,j(t+1) = best(xj) × (MoPr) × ((Uj − Lj) × 𝜇 + Lj),其中xi(t+1)表示第(t+1)次迭代时的第i个解,x(i,j)(t)表示当前代中第i个个体的第j个位置,best(xj)表示当前最佳解的第j个位置,ε是一个小的正数,第j个位置值的上限和下限分别用Uj和Lj表示。控制参数μ设置为0.5。
如果不满足MoA概率,则在AOA中执行利用策略(解细化)。该策略是使用减法(S)或加法(A)运算符开发的。在这里,μ也是一个常数,根据作者的意图固定为0.5。
xi,j(t+1) = best(xj) − (MoPr) × ((Uj − Lj) × 𝜇 + Lj),如果rand3 < 0.5
否则,xi,j(t+1) = best(xj) + (MoPr) × ((Uj − Lj) × 𝜇 + Lj)。
在AOA中,参数𝜇和θ非常重要,因为它们参与了探索与利用之间的权衡平衡。保持探索与利用的良好平衡通常是一项极具挑战性的任务。在原始AOA中,𝜇的值在探索和利用时均固定为0.5。然而,影响迭代过程中操作效率的θ参数设置为5。作者对𝜇和θ的不同值进行了实验,发现对于不同维度的单峰和多峰测试函数,𝜇 = 0.5和θ = 5通常能产生最佳结果。
现在,让我们来实现AOA算法的伪代码:
将迭代次数(epoch数量)增加1
// 开始初始化
如果是首次启动
对于种群中的每个个体:
对于每个坐标:
在允许范围内设置一个随机位置
将位置调整为离散值
标记初始化已完成
结束函数
结束条件判断
// 主优化过程
计算MoA = minMoA + EpochNumber * ((maxMoA - minMoА) / totalEpochs)
计算MoP = 1 - (EpochNumber / totalEpochs)^(1/θ)
// 解决方案空间探索阶段
对于种群中的每个个体:
对于每个坐标:
生成三个随机值(rand1、rand2、rand3)
获取该坐标的已知最佳值
如果rand1 < MoAc
如果rand2 > 0.5
使用除法更新位置
否则:
使用乘法更新位置
结束条件判断
除此之外:
如果rand3 > 0.5
使用减法更新位置
否则:
使用加法更新位置
结束条件判断
结束条件判断
将新位置调整为可接受的离散值
更新最优解
现在,我们继续编写代码。C_AO_AOA类是AOA算法的一种实现,旨在使用基于算术运算的方法解决优化问题。公有方法:
1. SetParams ()方法:从params数组中设置参数值。该方法允许在算法初始化后更改算法参数。
2. Init ()方法:
- 通过获取最小和最大搜索范围、搜索步长和迭代次数来初始化算法。
- 如果初始化成功,返回true,否则返回false。
3. Moving ()方法:在解决方案空间中移动个体。该方法根据给定参数和当前状态实现更新个体位置的逻辑。
4. Revision()方法:修改个体的当前位置,更新找到的函数最佳值以及对应个体的坐标。
私有字段,类参数:
- minT — MoA概率的最小值。
- maxT — MoA概率的最大值。
- θ — 影响平衡探索与利用的参数。
- μ — 用于控制个体位置变化(移动范围)的参数。
- ϵ — 用于防止除零的小数。
算法状态信息:
- epochs — 算法将经历的总迭代次数。
- epochNow — 当前迭代次数,用于跟踪算法进度,影响MoA概率和MoP比率。
C_AO_AOA类实现了AOA算法的主要组件,包括初始化、粒子移动和修改。
//—————————————————————————————————————————————————————————————————————————————— class C_AO_AOA : public C_AO { public: //-------------------------------------------------------------------- ~C_AO_AOA () { } C_AO_AOA () { ao_name = "AOA"; ao_desc = "Arithmetic Optimization Algorithm"; ao_link = "https://www.mql5.com/en/articles/16364"; popSize = 50; // Population size minT = 0.1; // Minimum T value maxT = 0.9; // Maximum T value θ = 10; // θ parameter μ = 0.01; // μ parameter ArrayResize (params, 5); // Resize the parameter array // Initialize parameters params [0].name = "popSize"; params [0].val = popSize; params [1].name = "minT"; params [1].val = minT; params [2].name = "maxT"; params [2].val = maxT; params [3].name = "θ"; params [3].val = θ; params [4].name = "μ"; params [4].val = μ; } void SetParams () // Method for setting parameters { popSize = (int)params [0].val; // Set population size minT = params [1].val; // Set minimum T maxT = params [2].val; // Set maximum T θ = params [3].val; // Set θ μ = params [4].val; // Set μ } bool Init (const double &rangeMinP [], // Minimum search range const double &rangeMaxP [], // Maximum search range const double &rangeStepP [], // Search step const int epochsP = 0); // Number of epochs void Moving (); // Method of moving particles void Revision (); // Revision method //---------------------------------------------------------------------------- double minT; // Minimum T value double maxT; // Maximum T value double θ; // θ parameter double μ; // μ parameter double ϵ; // Parameter to prevent division by zero private: //------------------------------------------------------------------- int epochs; // Total number of epochs int epochNow; // Current epoch }; //——————————————————————————————————————————————————————————————————————————————
C_AO_AOA类的Init方法负责通过设置搜索范围、步长以及将执行优化的迭代次数(epoch数)等参数,来初始化优化算法。方法逻辑:
1. 该方法首先调用StandardInit,对算法的标准参数进行初始化。如果此次初始化失败,Init方法将立即终止执行并返回false。
2. 参数设置界面
- 根据传入的epochsP参数设置总epochs(迭代次数)。
- 将当前epochNow(当前迭代次数)初始化为0。
- 将ϵ(用于防止除0的小数值)设置为DBL_EPSILON,这是双精度浮点类型所能表示的最小正数的标准值。
3. 如果所有步骤均成功完成,该方法将返回true,表示算法已成功初始化。
Init方法是准备算法执行的重要环节,因为它设置了优化过程中将使用的基本参数。调用此方法会将所有参数和变量重置为初始状态。
//—————————————————————————————————————————————————————————————————————————————— bool C_AO_AOA::Init (const double &rangeMinP [], // Minimum search range const double &rangeMaxP [], // Maximum search range const double &rangeStepP [], // Search step const int epochsP = 0) // Number of epochs { if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false; // Initialization of standard parameters //---------------------------------------------------------------------------- epochs = epochsP; // Set the total number of epochs epochNow = 0; // Initialize the current epoch ϵ = DBL_EPSILON; // Set ϵ return true; // Return 'true' if initialization was successful } //——————————————————————————————————————————————————————————————————————————————
Moving方法负责在AOA(算术优化算法)中实现个体在解空间内的移动。基于当前状态和算法参数,实现了更新个体位置的基本逻辑。方法逻辑:
1. 将epochNow(当前迭代次数)加1,以表明已进入新的优化阶段。
2. 初始随机定位:如果尚未进行任何更新(修订),则为每个粒子在给定的rangeMin(最小范围)和rangeMax(最大范围)内生成的随机位置。然后,使用指定的步长,通过SeInDiSp方法对每个位置进行离散化处理。
3. 根据当前迭代次数和指定参数计算MoAc (算术优化控制参数)和MoPr (算术优化概率参数)。这些值决定了用于更新个体位置的概率。
4. 探索阶段。对于每个个体和每个坐标,根据随机值和计算出的参数更新位置。位置可以使用各种运算符(除法和乘法)以及概率条件进行更新。
5. 更新后,同样使用SeInDiSp方法将位置转换为离散值。
//—————————————————————————————————————————————————————————————————————————————— // Particle displacement method void C_AO_AOA::Moving () { epochNow++; // Increase the current epoch number // Initial random positioning if (!revision) // If there has not been a revision yet { for (int i = 0; i < popSize; i++) // For each particle { for (int c = 0; c < coords; c++) // For each coordinate { a [i].c [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]); // Generate random position a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); // Convert to discrete values } } revision = true; // Set revision flag return; // Exit the method } //---------------------------------------------------------------------------- double MoAc = minT + epochNow * ((maxT - minT) / epochs); // Calculate the MoAc value double MoPr = 1.0 - pow (epochNow / epochs, (1.0 / θ)); // Calculate the MoPr value double best = 0.0; // Variable to store the best value // Research phase using Division (D) and Multiplication (M) operators for (int i = 0; i < popSize; i++) // For each particle { for (int c = 0; c < coords; c++) // For each coordinate { double rand1 = u.RNDprobab (); // Generate a random value double rand2 = u.RNDprobab (); // Generate a random value double rand3 = u.RNDprobab (); // Generate a random value best = cB [c]; // Save the current best value if (rand1 < MoAc) // If random value is less than MoAc { if (rand2 > 0.5) // If random value is greater than 0.5 { a [i].c [c] = best / (MoPr + ϵ) * ((rangeMax [c] - rangeMin [c]) * μ + rangeMin [c]); // Update particle position } else { a [i].c [c] = best * (MoPr) * ((rangeMax [c] - rangeMin [c]) * μ + rangeMin [c]); // Update particle position } } else // If random value is greater than or equal to MoAc { if (rand3 > 0.5) // If random value is greater than 0.5 { a [i].c [c] = best - (MoPr) * ((rangeMax [c] - rangeMin [c]) * μ + rangeMin [c]); // Update particle position } else { a [i].c [c] = best + (MoPr) * ((rangeMax [c] - rangeMin [c]) * μ + rangeMin [c]); // Update particle position } } a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); // Convert to discrete values } } } //——————————————————————————————————————————————————————————————————————————————
在C_AO_AOA类中,Revision方法用于更新种群中最优个体的相关信息。该方法遍历所有个体,将它们的函数值与当前最优值进行比较,若发现更优的值,则更新该最优值并保存对应个体的索引。随后,将最优个体的坐标复制到cB数组中。
//—————————————————————————————————————————————————————————————————————————————— void C_AO_AOA::Revision () { int ind = -1; // Index to store the best particle for (int i = 0; i < popSize; i++) // For each particle { if (a [i].f > fB) // If the function value is better than the current best one { fB = a [i].f; // Update the best value of the function ind = i; // Save the index of the best particle } } if (ind != -1) ArrayCopy (cB, a [ind].c, 0, 0, WHOLE_ARRAY); // Copy the coordinates of the best particle } //——————————————————————————————————————————————————————————————————————————————
C_AO_Utilities类的SeInDiSp方法用于将输入值In限制在指定步长Step的[InMin, InMax]范围内。
1. 若In小于或等于 InMin,则返回InMin。
2. 若In大于或等于InMax,则返回InMax。
3. 若Step等于0,则返回In的原始值。
4. 否则,将值四舍五入到(In - InMin) / Step,并返回考虑步长后调整至该范围内的值。
double C_AO_Utilities :: SeInDiSp (double In, double InMin, double InMax, double Step) { if (In <= InMin) return (InMin); if (In >= InMax) return (InMax); if (Step == 0.0) return (In); else return (InMin + Step * (double)MathRound ((In - InMin) / Step)); }
测试结果
AOA算法相当简单,让我们来看一下它在测试任务上的表现。
AOA|Arithmetic Optimization Algorithm|50.0|0.1|0.9|2.0|0.01|
=============================
5 Hilly's; Func runs: 10000; result: 0.3914957505847635
25 Hilly's; Func runs: 10000; result: 0.27733670012505607
500 Hilly's; Func runs: 10000; result: 0.2514517003089684
=============================
5 Forest's; Func runs: 10000; result: 0.23495704012464264
25 Forest's; Func runs: 10000; result: 0.1853447250852242
500 Forest's; Func runs: 10000; result: 0.15382470751079919
=============================
5 Megacity's; Func runs: 10000; result: 0.19846153846153847
25 Megacity's; Func runs: 10000; result: 0.11815384615384619
500 Megacity's; Func runs: 10000; result: 0.09475384615384692
=============================
总分:1.90578 (21.18%)
根据测试结果,该算法在满分100分的情况下仅获得21.18分。这一成绩非常不理想。遗憾的是,它在当前排名表中位列末尾。让我们尝试修改算法逻辑,以取得更好的结果。我们将逐步进行修改,并监测结果。
原始AOA算法的逻辑涉及随机搜索,其仅依赖于从四种数学运算符中做出概率性选择的随机性本质。让我们在μ位移比例中加入一个随机性元素,将其乘以一个0到1之间的随机数。
// Research phase using Division (D) and Multiplication (M) operators for (int i = 0; i < popSize; i++) // For each particle { for (int c = 0; c < coords; c++) // For each coordinate { double rand1 = u.RNDprobab (); // Generate a random value double rand2 = u.RNDprobab (); // Generate a random value double rand3 = u.RNDprobab (); // Generate a random value best = cB [c]; // Save the current best value μ *= u.RNDfromCI (0, 1); // Random change of μ if (rand1 < MoAc) // If random value is less than MoAc { if (rand2 > 0.5) // If random value is greater than 0.5 { a [i].c [c] = best / (MoPr + ϵ) * ((rangeMax [c] - rangeMin [c]) * μ + rangeMin [c]); // Update particle position } else { a [i].c [c] = best * (MoPr) * ((rangeMax [c] - rangeMin [c]) * μ + rangeMin [c]); // Update particle position } } else // If random value is greater than or equal to MoAc { if (rand3 > 0.5) // If random value is greater than 0.5 { a [i].c [c] = best - (MoPr) * ((rangeMax [c] - rangeMin [c]) * μ + rangeMin [c]); // Update particle position } else { a [i].c [c] = best + (MoPr) * ((rangeMax [c] - rangeMin [c]) * μ + rangeMin [c]); // Update particle position } } a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); // Convert to discrete values } }
让我们使用相同的参数测试算法:
AOA|Arithmetic Optimization Algorithm|50.0|0.1|0.9|2.0|0.01|
=============================
5 Hilly's; Func runs: 10000; result: 0.3595591180258857
25 Hilly's; Func runs: 10000; result: 0.2804913285516192
500 Hilly's; Func runs: 10000; result: 0.25204298245610646
=============================
5 Forest's; Func runs: 10000; result: 0.24115834887873383
25 Forest's; Func runs: 10000; result: 0.18034196700384764
500 Forest's; Func runs: 10000; result: 0.15441446106797124
=============================
5 Megacity's; Func runs: 10000; result: 0.18307692307692305
25 Megacity's; Func runs: 10000; result: 0.12400000000000003
500 Megacity's; Func runs: 10000; result: 0.09470769230769309
=============================
总分:1.86979 (20.78%)
遗憾的是,结果变得更糟了。需要采取更多措施。不过,在确定性表达式中加入随机性这一做法本身,理应能提升搜索策略的多样性。让我们仔细审视数学运算符的公式——每个公式都包含rangeMin [c]这一项。本质上,这些运算符所得出的表达式总是以待优化参数的最小边界为中心。这并无明显逻辑依据,因此,让我们将这一元素从所有公式中移除。
// Research phase using Division (D) and Multiplication (M) operators for (int i = 0; i < popSize; i++) // For each particle { for (int c = 0; c < coords; c++) // For each coordinate { double rand1 = u.RNDprobab (); // Generate a random value double rand2 = u.RNDprobab (); // Generate a random value double rand3 = u.RNDprobab (); // Generate a random value best = cB [c]; // Save the current best value μ *= u.RNDfromCI (0, 1); // Change μ if (rand1 < MoAc) // If random value is less than MoAc { if (rand2 > 0.5) // If random value is greater than 0.5 { a [i].c [c] = best / (MoPr + ϵ) * ((rangeMax [c] - rangeMin [c]) * μ);// + rangeMin [c]); // Update particle position } else { a [i].c [c] = best * (MoPr) * ((rangeMax [c] - rangeMin [c]) * μ);// + rangeMin [c]); // Update particle position } } else // If random value is greater than or equal to MoAc { if (rand3 > 0.5) // If random value is greater than 0.5 { a [i].c [c] = best - (MoPr) * ((rangeMax [c] - rangeMin [c]) * μ);// + rangeMin [c]); // Update particle position } else { a [i].c [c] = best + (MoPr) * ((rangeMax [c] - rangeMin [c]) * μ);// + rangeMin [c]); // Update particle position } } a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); // Convert to discrete values } }
让我们来进行测试。以下是所得结果:
AOA|Arithmetic Optimization Algorithm|50.0|0.1|0.9|2.0|0.01|
=============================
5 Hilly's; Func runs: 10000; result: 0.36094646986361645
25 Hilly's; Func runs: 10000; result: 0.28294095623218063
500 Hilly's; Func runs: 10000; result: 0.2524581968477915
=============================
5 Forest's; Func runs: 10000; result: 0.2463208325927641
25 Forest's; Func runs: 10000; result: 0.1772140022690996
500 Forest's; Func runs: 10000; result: 0.15367993432040622
=============================
5 Megacity's; Func runs: 10000; result: 0.1923076923076923
25 Megacity's; Func runs: 10000; result: 0.11938461538461542
500 Megacity's; Func runs: 10000; result: 0.09433846153846229
=============================
总分:1.87959 (20.88%)
我们所做的改动并未带来任何性能提升,考虑到我们已对搜索策略实施了重大调整,这一结果着实令人诧异。这或许表明,策略本身存在缺陷,而仅移除个别组件并不会对结果产生显著影响。
搜索策略中包含一个随每次迭代线性增加的MoA组件(见图1)。让我们尝试将该组件用于从最优解的坐标中做出概率性选择,并将其复制到当前工作解中。通过利用种群中最优解的信息交换(在原始版本中,智能体之间不存在信息交换),应该为搜索策略增添组合特性。
// Research phase using Division (D) and Multiplication (M) operators for (int i = 0; i < popSize; i++) // For each particle { for (int c = 0; c < coords; c++) // For each coordinate { double rand1 = u.RNDprobab (); // Generate a random value double rand2 = u.RNDprobab (); // Generate a random value double rand3 = u.RNDprobab (); // Generate a random value best = cB [c]; // Save the current best value μ *= u.RNDfromCI (0, 1); // Change μ if (rand1 < MoAc) // If random value is less than MoAc { if (rand2 > 0.5) // If random value is greater than 0.5 { a [i].c [c] = best / (MoPr + ϵ) * ((rangeMax [c] - rangeMin [c]) * μ); // Update particle position } else { a [i].c [c] = best * (MoPr) * ((rangeMax [c] - rangeMin [c]) * μ); // Update particle position } } else // If random value is greater than or equal to MoAc { if (rand3 > 0.5) // If random value is greater than 0.5 { a [i].c [c] = best - (MoPr) * ((rangeMax [c] - rangeMin [c]) * μ); // Update particle position } else { a [i].c [c] = best + (MoPr) * ((rangeMax [c] - rangeMin [c]) * μ); // Update particle position } } if (u.RNDbool () < MoAc) a [i].c [c] = cB [c]; // Set to the best value a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); // Convert to discrete values } }
在引入通过最优解进行信息交换的逻辑后,取得的结果如下:
AOA|Arithmetic Optimization Algorithm|50.0|0.1|0.9|2.0|0.01|
=============================
5 Hilly's; Func runs: 10000; result: 0.360814744695913
25 Hilly's; Func runs: 10000; result: 0.28724958448168375
500 Hilly's; Func runs: 10000; result: 0.2523432997811412
=============================
5 Forest's; Func runs: 10000; result: 0.26319762212870146
25 Forest's; Func runs: 10000; result: 0.1796822846691542
500 Forest's; Func runs: 10000; result: 0.1546335398592898
=============================
5 Megacity's; Func runs: 10000; result: 0.18
25 Megacity's; Func runs: 10000; result: 0.12153846153846157
500 Megacity's; Func runs: 10000; result: 0.09373846153846228
=============================
总分:1.89320 (21.04%)
我们观察到性能有所提升,但目前这种提升仍局限于解本身的范围内。现在,让我们在代码的同一部分添加一项功能:在优化参数的整个可接受范围内,为坐标生成随机值的概率(同时,该坐标会根据MoP方程以非线性方式递减)。
// Probabilistic update of particle position if (u.RNDbool () < MoAc) a [i].c [c] = cB [c]; // Set to the best value else if (u.RNDbool () < MoPr) a [i].c [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]); // Generate new random position
让我们看看以下结果:
AOA|Arithmetic Optimization Algorithm|50.0|0.1|0.9|2.0|0.01|
=============================
5 Hilly's; Func runs: 10000; result: 0.8354927331645667
25 Hilly's; Func runs: 10000; result: 0.3963221867834875
500 Hilly's; Func runs: 10000; result: 0.2526544322311671
=============================
5 Forest's; Func runs: 10000; result: 0.7689954585427405
25 Forest's; Func runs: 10000; result: 0.3144560745800252
500 Forest's; Func runs: 10000; result: 0.15495875390289315
=============================
5 Megacity's; Func runs: 10000; result: 0.6076923076923076
25 Megacity's; Func runs: 10000; result: 0.24646153846153843
500 Megacity's; Func runs: 10000; result: 0.09816923076923163
=============================
总分:3.67520 (40.84%)
令人惊叹的是,效率得到了大幅提升!这意味着我们的改进方向是正确的。值得注意的是,我们究竟为AOA算法逻辑添加了什么:在优化初期,即第一个迭代周期(epoch)时,将全局最优解的坐标复制到当前解的概率极低。这十分合理,因为在优化初始阶段,策略才刚刚开始探索搜索空间。与此同时,在第一次迭代时,在整个搜索空间内生成随机解的概率达到最大。在所有迭代周期中,这些概率会发生变化:复制全局解坐标的概率逐渐增加,而生成随机解的概率则相反,逐渐降低(见图1)。
鉴于我所做的改动已使性能得到提升,且此前已指出对原始逻辑的改动并未带来显著改善,因此,完全摒弃所有算术运算符是合理的。让我们基于测试问题检验修改后的算法:
AOA|Arithmetic Optimization Algorithm|50.0|0.1|0.9|2.0|
=============================
5 Hilly's; Func runs: 10000; result: 0.8751771961221438
25 Hilly's; Func runs: 10000; result: 0.4645369071659114
500 Hilly's; Func runs: 10000; result: 0.27170038319811357
=============================
5 Forest's; Func runs: 10000; result: 0.8369443889312367
25 Forest's; Func runs: 10000; result: 0.36483865328371257
500 Forest's; Func runs: 10000; result: 0.17097532914778202
=============================
5 Megacity's; Func runs: 10000; result: 0.7046153846153846
25 Megacity's; Func runs: 10000; result: 0.28892307692307695
500 Megacity's; Func runs: 10000; result: 0.10847692307692398
=============================
总分:4.08619 (45.40%)
正如我们所见,效率又提升了近5%,这再次验证了我的推理是正确的。使用默认参数时,我们获得了有趣的结果,然而,逻辑改动如此之大,以至于现在有必要为算法选择最优的外部参数。效率提升带来的额外好处包括:
- 工作速度显著提升,因为我们摆脱了不必要的随机数生成过程
- 外部参数数量减少了一个
让我们来看看最终得到的算法结果:
AOA|Arithmetic Optimization Algorithm|50.0|0.1|0.5|10.0|
=============================
5 Hilly's; Func runs: 10000; result: 0.9152036654779877
25 Hilly's; Func runs: 10000; result: 0.46975580956945456
500 Hilly's; Func runs: 10000; result: 0.27088799720164297
=============================
5 Forest's; Func runs: 10000; result: 0.8967497776673259
25 Forest's; Func runs: 10000; result: 0.3740125122006007
500 Forest's; Func runs: 10000; result: 0.16983896751516864
=============================
5 Megacity's; Func runs: 10000; result: 0.6953846153846155
25 Megacity's; Func runs: 10000; result: 0.2803076923076923
500 Megacity's; Func runs: 10000; result: 0.10852307692307792
=============================
总分:4.18066 (46.45%)
所得结果已足以在排名表中占据一席之地,而最终版本的搜索策略已完全摒弃了原始逻辑的元素。因此,我决定为这个新算法赋予一个新名称——简单优化算法(Simple Optimization Algorithm,SOA)。让我们来看看完整的SOA算法最终代码。
#include "#C_AO.mqh" //—————————————————————————————————————————————————————————————————————————————— class C_AO_SOA : public C_AO { public: //-------------------------------------------------------------------- ~C_AO_SOA () { } C_AO_SOA () { ao_name = "SOA"; ao_desc = "Simple Optimization Algorithm"; ao_link = "https://www.mql5.com/en/articles/16364"; popSize = 50; // Population size minT = 0.1; // Minimum T value maxT = 0.9; // Maximum T value θ = 10; // θ parameter ArrayResize (params, 4); // Resize the parameter array // Initialize parameters params [0].name = "popSize"; params [0].val = popSize; params [1].name = "minT"; params [1].val = minT; params [2].name = "maxT"; params [2].val = maxT; params [3].name = "θ"; params [3].val = θ; } void SetParams () // Method for setting parameters { popSize = (int)params [0].val; // Set population size minT = params [1].val; // Set minimum T maxT = params [2].val; // Set maximum T θ = params [3].val; // Set θ } bool Init (const double &rangeMinP [], // Minimum search range const double &rangeMaxP [], // Maximum search range const double &rangeStepP [], // Search step const int epochsP = 0); // Number of epochs void Moving (); // Method of moving particles void Revision (); // Revision method //---------------------------------------------------------------------------- double minT; // Minimum T value double maxT; // Maximum T value double θ; // θ parameter private: //------------------------------------------------------------------- int epochs; // Total number of epochs int epochNow; // Current epoch double ϵ; // Parameter to prevent division by zero }; //—————————————————————————————————————————————————————————————————————————————— //—————————————————————————————————————————————————————————————————————————————— bool C_AO_SOA::Init (const double &rangeMinP [], // Minimum search range const double &rangeMaxP [], // Maximum search range const double &rangeStepP [], // Search step const int epochsP = 0) // Number of epochs { if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false; // Initialization of standard parameters //---------------------------------------------------------------------------- epochs = epochsP; // Set the total number of epochs epochNow = 0; // Initialize the current epoch ϵ = DBL_EPSILON; // Set ϵ return true; // Return 'true' if initialization was successful } //—————————————————————————————————————————————————————————————————————————————— //—————————————————————————————————————————————————————————————————————————————— // Particle displacement method void C_AO_SOA::Moving () { epochNow++; // Increase the current epoch number // Initial random positioning if (!revision) // If there has not been a revision yet { for (int i = 0; i < popSize; i++) // For each particle { for (int c = 0; c < coords; c++) // For each coordinate { a [i].c [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]); // Generate random position a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); // Convert to discrete values } } revision = true; // Set revision flag return; // Exit the method } //---------------------------------------------------------------------------- double MoAc = minT + epochNow * ((maxT - minT) / epochs); // Calculate the MoAc value double MoPr = 1.0 - pow (epochNow / epochs, (1.0 / θ)); // Calculate the MoPr value double best = 0.0; // Variable to store the best value // Research phase using Division (D) and Multiplication (M) operators for (int i = 0; i < popSize; i++) // For each particle { for (int c = 0; c < coords; c++) // For each coordinate { // Probabilistic update of particle position if (u.RNDbool () < MoAc) a [i].c [c] = cB [c]; // Set to the best value else if (u.RNDbool () < MoPr) a [i].c [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]); // Generate new random position a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); // Convert to discrete values } } } //—————————————————————————————————————————————————————————————————————————————— //—————————————————————————————————————————————————————————————————————————————— void C_AO_SOA::Revision () { int ind = -1; // Index to store the best particle for (int i = 0; i < popSize; i++) // For each particle { if (a [i].f > fB) // If the function value is better than the current best one { fB = a [i].f; // Update the best value of the function ind = i; // Save the index of the best particle } } if (ind != -1) ArrayCopy (cB, a [ind].c, 0, 0, WHOLE_ARRAY); // Copy the coordinates of the best particle } //——————————————————————————————————————————————————————————————————————————————
就代码而言,这是我们之前探讨过的最为简洁的优化算法之一。唯一比它更简短的代码是随机游走(Random Walk,RW)算法的代码,该算法将在后续的一篇文章中详细讨论。
在解空间中可视化搜索策略的性能,能直观展现诸多信息。我将AOA算法在三种函数上测试,将其在丘陵(Hilly)、森林(Forest)和超级城市(Megacity)中的表现整合成了一部动画,因为该算法在不同类型任务上的性能表现几乎毫无差异。以下是SOA算法在三个测试函数上分别运行的可视化效果。
值得注意的是,新的SOA算法在处理低维问题时表现突出——结果分布范围较小,这是一种相当罕见的优良特性。
AOA在Hilly, Forest, Megacity 测试函数上
SOA在Hilly测试函数上
SOA在Forest测试函数上
SOA在Megacity测试函数上
根据测试结果,原始的AOA算法未能跻身排名表,其成绩位列45名之后。而新的简单优化算法(SOA)则成功入榜,最终排名第29位。
# | AO | 描述 | Hilly值 | Hilly最终值 | Forest值 | Forest最终值 | Megacity (离散) | Megacity最终值 | 最终结果 | 最大百分比 | ||||||
10 p (5 F) | 50 p (25 F) | 1000 p (500 F) | 10 p (5 F) | 50 p (25 F) | 1000 p (500 F) | 10 p (5 F) | 50 p (25 F) | 1000 p (500 F) | ||||||||
1 | ANS | 跨邻域搜索 | 0.94948 | 0.84776 | 0.43857 | 2.23581 | 1.00000 | 0.92334 | 0.39988 | 2.32323 | 0.70923 | 0.63477 | 0.23091 | 1.57491 | 6.134 | 68.15 |
2 | CLA | 密码锁算法(joo) | 0.95345 | 0.87107 | 0.37590 | 2.20042 | 0.98942 | 0.91709 | 0.31642 | 2.22294 | 0.79692 | 0.69385 | 0.19303 | 1.68380 | 6.107 | 67.86 |
3 | AMOm | 动物迁徙优化M | 0.90358 | 0.84317 | 0.46284 | 2.20959 | 0.99001 | 0.92436 | 0.46598 | 2.38034 | 0.56769 | 0.59132 | 0.23773 | 1.39675 | 5.987 | 66.52 |
4 | (P+O)ES | (P+O) 进化策略 | 0.92256 | 0.88101 | 0.40021 | 2.20379 | 0.97750 | 0.87490 | 0.31945 | 2.17185 | 0.67385 | 0.62985 | 0.18634 | 1.49003 | 5.866 | 65.17 |
5 | CTA | 彗星尾算法(joo) | 0.95346 | 0.86319 | 0.27770 | 2.09435 | 0.99794 | 0.85740 | 0.33949 | 2.19484 | 0.88769 | 0.56431 | 0.10512 | 1.55712 | 5.846 | 64.96 |
6 | SDSm | 随机扩散搜索 M | 0.93066 | 0.85445 | 0.39476 | 2.17988 | 0.99983 | 0.89244 | 0.19619 | 2.08846 | 0.72333 | 0.61100 | 0.10670 | 1.44103 | 5.709 | 63.44 |
7 | AAm | 射箭算法M | 0.91744 | 0.70876 | 0.42160 | 2.04780 | 0.92527 | 0.75802 | 0.35328 | 2.03657 | 0.67385 | 0.55200 | 0.23738 | 1.46323 | 5.548 | 61.64 |
8 | ESG | 社会群体的进化(joo) | 0.99906 | 0.79654 | 0.35056 | 2.14616 | 1.00000 | 0.82863 | 0.13102 | 1.95965 | 0.82333 | 0.55300 | 0.04725 | 1.42358 | 5.529 | 61.44 |
9 | SIA | 模拟各向同性退火(joo) | 0.95784 | 0.84264 | 0.41465 | 2.21513 | 0.98239 | 0.79586 | 0.20507 | 1.98332 | 0.68667 | 0.49300 | 0.09053 | 1.27020 | 5.469 | 60.76 |
10 | ACS | 人工协同搜索 | 0.75547 | 0.74744 | 0.30407 | 1.80698 | 1.00000 | 0.88861 | 0.22413 | 2.11274 | 0.69077 | 0.48185 | 0.13322 | 1.30583 | 5.226 | 58.06 |
11 | ASO | 无序社会优化 | 0.84872 | 0.74646 | 0.31465 | 1.90983 | 0.96148 | 0.79150 | 0.23803 | 1.99101 | 0.57077 | 0.54062 | 0.16614 | 1.27752 | 5.178 | 57.54 |
12 | AOSm | 原子轨道搜索M | 0.80232 | 0.70449 | 0.31021 | 1.81702 | 0.85660 | 0.69451 | 0.21996 | 1.77107 | 0.74615 | 0.52862 | 0.14358 | 1.41835 | 5.006 | 55.63 |
13 | TSEA | 龟壳演化算法(joo) | 0.96798 | 0.64480 | 0.29672 | 1.90949 | 0.99449 | 0.61981 | 0.22708 | 1.84139 | 0.69077 | 0.42646 | 0.13598 | 1.25322 | 5.004 | 55.60 |
14 | DE | 差分进化 | 0.95044 | 0.61674 | 0.30308 | 1.87026 | 0.95317 | 0.78896 | 0.16652 | 1.90865 | 0.78667 | 0.36033 | 0.02953 | 1.17653 | 4.955 | 55.06 |
15 | CRO | 化学反应优化 | 0.94629 | 0.66112 | 0.29853 | 1.90593 | 0.87906 | 0.58422 | 0.21146 | 1.67473 | 0.75846 | 0.42646 | 0.12686 | 1.31178 | 4.892 | 54.36 |
16 | BSA | 鸟群算法 | 0.89306 | 0.64900 | 0.26250 | 1.80455 | 0.92420 | 0.71121 | 0.24939 | 1.88479 | 0.69385 | 0.32615 | 0.10012 | 1.12012 | 4.809 | 53.44 |
17 | HS | 和声搜索 | 0.86509 | 0.68782 | 0.32527 | 1.87818 | 0.99999 | 0.68002 | 0.09590 | 1.77592 | 0.62000 | 0.42267 | 0.05458 | 1.09725 | 4.751 | 52.79 |
18 | SSG | 树苗播种和生长 | 0.77839 | 0.64925 | 0.39543 | 1.82308 | 0.85973 | 0.62467 | 0.17429 | 1.65869 | 0.64667 | 0.44133 | 0.10598 | 1.19398 | 4.676 | 51.95 |
19 | BCOm | 细菌趋化性优化算法M | 0.75953 | 0.62268 | 0.31483 | 1.69704 | 0.89378 | 0.61339 | 0.22542 | 1.73259 | 0.65385 | 0.42092 | 0.14435 | 1.21912 | 4.649 | 51.65 |
20 | ABO | 非洲水牛优化 | 0.83337 | 0.62247 | 0.29964 | 1.75548 | 0.92170 | 0.58618 | 0.19723 | 1.70511 | 0.61000 | 0.43154 | 0.13225 | 1.17378 | 4.634 | 51.49 |
21 | (PO)ES | (PO) 进化策略 | 0.79025 | 0.62647 | 0.42935 | 1.84606 | 0.87616 | 0.60943 | 0.19591 | 1.68151 | 0.59000 | 0.37933 | 0.11322 | 1.08255 | 4.610 | 51.22 |
22 | TSm | 禁忌搜索M | 0.87795 | 0.61431 | 0.29104 | 1.78330 | 0.92885 | 0.51844 | 0.19054 | 1.63783 | 0.61077 | 0.38215 | 0.12157 | 1.11449 | 4.536 | 50.40 |
23 | BSO | 头脑风暴优化 | 0.93736 | 0.57616 | 0.29688 | 1.81041 | 0.93131 | 0.55866 | 0.23537 | 1.72534 | 0.55231 | 0.29077 | 0.11914 | 0.96222 | 4.498 | 49.98 |
24 | WOAm | 鲸鱼优化算法M | 0.84521 | 0.56298 | 0.26263 | 1.67081 | 0.93100 | 0.52278 | 0.16365 | 1.61743 | 0.66308 | 0.41138 | 0.11357 | 1.18803 | 4.476 | 49.74 |
25 | AEFA | 人工电场算法 | 0.87700 | 0.61753 | 0.25235 | 1.74688 | 0.92729 | 0.72698 | 0.18064 | 1.83490 | 0.66615 | 0.11631 | 0.09508 | 0.87754 | 4.459 | 49.55 |
26 | AEO | 基于人工生态系统的优化算法 | 0.91380 | 0.46713 | 0.26470 | 1.64563 | 0.90223 | 0.43705 | 0.21400 | 1.55327 | 0.66154 | 0.30800 | 0.28563 | 1.25517 | 4.454 | 49.49 |
27 | ACOm | 蚁群优化 M | 0.88190 | 0.66127 | 0.30377 | 1.84693 | 0.85873 | 0.58680 | 0.15051 | 1.59604 | 0.59667 | 0.37333 | 0.02472 | 0.99472 | 4.438 | 49.31 |
28 | BFO-GA | 细菌觅食优化 - ga | 0.89150 | 0.55111 | 0.31529 | 1.75790 | 0.96982 | 0.39612 | 0.06305 | 1.42899 | 0.72667 | 0.27500 | 0.03525 | 1.03692 | 4.224 | 46.93 |
29 | SOA | 简单优化算法 | 0.91520 | 0.46976 | 0.27089 | 1.65585 | 0.89675 | 0.37401 | 0.16984 | 1.44060 | 0.69538 | 0.28031 | 0.10852 | 1.08422 | 4.181 | 46.45 |
30 | ABHA | 人工蜂巢算法 | 0.84131 | 0.54227 | 0.26304 | 1.64663 | 0.87858 | 0.47779 | 0.17181 | 1.52818 | 0.50923 | 0.33877 | 0.10397 | 0.95197 | 4.127 | 45.85 |
31 | ACMO | 大气云模型优化 | 0.90321 | 0.48546 | 0.30403 | 1.69270 | 0.80268 | 0.37857 | 0.19178 | 1.37303 | 0.62308 | 0.24400 | 0.10795 | 0.97503 | 4.041 | 44.90 |
32 | ASHA | 人工淋浴算法 | 0.89686 | 0.40433 | 0.25617 | 1.55737 | 0.80360 | 0.35526 | 0.19160 | 1.35046 | 0.47692 | 0.18123 | 0.09774 | 0.75589 | 3.664 | 40.71 |
33 | ASBO | 适应性社会行为优化 | 0.76331 | 0.49253 | 0.32619 | 1.58202 | 0.79546 | 0.40035 | 0.26097 | 1.45677 | 0.26462 | 0.17169 | 0.18200 | 0.61831 | 3.657 | 40.63 |
34 | MEC | 思维进化计算 | 0.69533 | 0.53376 | 0.32661 | 1.55569 | 0.72464 | 0.33036 | 0.07198 | 1.12698 | 0.52500 | 0.22000 | 0.04198 | 0.78698 | 3.470 | 38.55 |
35 | IWO | 入侵杂草优化 | 0.72679 | 0.52256 | 0.33123 | 1.58058 | 0.70756 | 0.33955 | 0.07484 | 1.12196 | 0.42333 | 0.23067 | 0.04617 | 0.70017 | 3.403 | 37.81 |
36 | Micro-AIS | 微型人工免疫系统 | 0.79547 | 0.51922 | 0.30861 | 1.62330 | 0.72956 | 0.36879 | 0.09398 | 1.19233 | 0.37667 | 0.15867 | 0.02802 | 0.56335 | 3.379 | 37.54 |
37 | COAm | 布谷鸟优化算法 M | 0.75820 | 0.48652 | 0.31369 | 1.55841 | 0.74054 | 0.28051 | 0.05599 | 1.07704 | 0.50500 | 0.17467 | 0.03380 | 0.71347 | 3.349 | 37.21 |
38 | SDOm | 螺旋动力学优化 M | 0.74601 | 0.44623 | 0.29687 | 1.48912 | 0.70204 | 0.34678 | 0.10944 | 1.15826 | 0.42833 | 0.16767 | 0.03663 | 0.63263 | 3.280 | 36.44 |
39 | NMm | Nelder-Mead方法 M | 0.73807 | 0.50598 | 0.31342 | 1.55747 | 0.63674 | 0.28302 | 0.08221 | 1.00197 | 0.44667 | 0.18667 | 0.04028 | 0.67362 | 3.233 | 35.92 |
40 | FAm | 萤火虫算法 M | 0.58634 | 0.47228 | 0.32276 | 1.38138 | 0.68467 | 0.37439 | 0.10908 | 1.16814 | 0.28667 | 0.16467 | 0.04722 | 0.49855 | 3.048 | 33.87 |
41 | GSA | 引力搜索算法 | 0.64757 | 0.49197 | 0.30062 | 1.44016 | 0.53962 | 0.36353 | 0.09945 | 1.00260 | 0.32667 | 0.12200 | 0.01917 | 0.46783 | 2.911 | 32.34 |
42 | BFO | 细菌觅食优化 | 0.61171 | 0.43270 | 0.31318 | 1.35759 | 0.54410 | 0.21511 | 0.05676 | 0.81597 | 0.42167 | 0.13800 | 0.03195 | 0.59162 | 2.765 | 30.72 |
43 | ABC | 人工蜂群 | 0.63377 | 0.42402 | 0.30892 | 1.36671 | 0.55103 | 0.21874 | 0.05623 | 0.82600 | 0.34000 | 0.14200 | 0.03102 | 0.51302 | 2.706 | 30.06 |
44 | BA | 蝙蝠算法 | 0.59761 | 0.45911 | 0.35242 | 1.40915 | 0.40321 | 0.19313 | 0.07175 | 0.66810 | 0.21000 | 0.10100 | 0.03517 | 0.34617 | 2.423 | 26.93 |
45 | AAA | 人工藻类算法 | 0.50007 | 0.32040 | 0.25525 | 1.07572 | 0.37021 | 0.22284 | 0.16785 | 0.76089 | 0.27846 | 0.14800 | 0.09755 | 0.52402 | 2.361 | 26.23 |
总结
我们已经详细地探讨了算术优化算法,其实现过程确实极为简便。然而,正如有时会出现的情况那样,简单性并不总能保证取得优异成果。成功的真正秘诀在于极致的简单!当然,我这是在开玩笑。AOA算法的主要问题在于种群成员之间缺乏互动与信息交流,这进而导致该搜索策略完全缺乏组合特性。
该算法的另一缺点在于其搜索算子缺乏可变性。尽管每个坐标的算子选择是随机的,但这无法使算法有效地“捕捉”到搜索空间的多维特征。不过,AOA算法也包含了一些合理且逻辑清晰的方法,比如MoA和MoP元素会随每个迭代周期(epoch)而变化。这些方法成为重新构建算法的基础,推动其演变为一种基于概率方法的新型、有趣且极其简单的搜索策略,该策略通过从种群中的最优解复制信息,并在搜索空间中生成随机解来实现。
随着每个迭代周期的推进,种群决策中的随机性逐渐减少,而成功方向的集中度则逐渐增加。可以将其比作在河上建造一座优雅的桥梁:在工作的初始阶段,会使用各种可能并不适合最终结果的材料和设计。然而,随着项目逐渐接近完成,最优解决方案变得更加显而易见,不必要的材料也被摒弃。最终,这座桥梁变得愈发平衡稳固,以优雅而坚固的方式连接着两岸。
图例2. 根据相关测试,将算法的颜色等级大于或等于0.99的结果以白色突出显示。
图例3. 算法测试结果的直方图(标尺从 0 到 100,数字越大结果越好,
其中 100 是理论上的最大可能结果,将计算评级表格的脚本存档)
SOA pros and cons:
优点:
- 少量外部参数。
- 在低维问题上,尤其是离散型问题上表现优异。
- 快速。
- 在低维问题上,结果分布范围(或离散程度)较小。
- 实现简单。
缺点:
- 可扩展性低。
文章附有一个包含当前版本算法代码的归档文件。本文作者对标准算法描述的绝对准确性不承担责任。为提升搜索能力,已经对其中的许多算法进行了修改。文章中表述的结论和论断都是基于实验的结果。
文中所用的程序
# | 名称 | 类型 | 描述 |
---|---|---|---|
1 | #C_AO.mqh | 库 | 种群优化算法的基类 |
2 | #C_AO_enum.mqh | 库 | 种群优化算法的枚举说明 |
3 | TestFunctions.mqh | 库 | 测试函数库 |
4 | TestStandFunctions.mqh | 库 | 测试台函数库 |
5 | Utilities.mqh | 库 | 辅助函数库 |
6 | CalculationTestResults.mqh | 库 | 用于计算比较表结果的脚本 |
7 | Testing AOs.mq5 | 脚本 | 面向所有种群优化算法的统一测试平台 |
8 | Simple use of population optimization algorithms.mq5 | 脚本 | 种群优化算法非可视化简易使用案例 |
9 | AO_AOA_ArithmeticOptimizationAlgorithm.mqh | 库 | AOA算法类 |
10 | AO_SOA_SimpleOptimizationAlgorithm.mqh | 库 | SOA算法类 |
11 | Test_AO_AOA.mq5 | 脚本 | AOA测试 |
12 | Test_AO_SOA.mq5 | 脚本 | SOA测试 |
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/16364

