3.3. Код юнит-тестов оператора мутации
namespace EvolutionaryAlgorithmTemplate.Tests
{
// FakeRandom позволяет задать последовательность возвращаемых значений для NextDouble()
public class FakeRandom : Random
{
private Queue<double> values;
public FakeRandom(IEnumerable<double> values)
{
this.values = new Queue<double>(values);
}
public override double NextDouble()
{
return values.Dequeue();
}
}
public class GenMutationTests
{
/*
Тест проверяет, что если rnd.NextDouble() возвращает значение больше mutationProbability,
мутация не происходит и значение гена остаётся неизменным.
*/
[Fact]
public void Mutation_ShouldNotChangeValue_WhenRandomValueIsAboveProbability()
{
double initialValue = 0.0;
double xmin = -5.0;
double xmax = 5.0;
double mutationProbability = 0.1;
// Первое значение 0.5 (>= 0.1) – мутация не срабатывает.
var fakeRandom = new FakeRandom(new List<double> { 0.5 });
Gen gene = new Gen(initialValue);
gene.Mutation(xmin, xmax, mutationProbability, fakeRandom);
// значение должно остаться неизменным
Assert.Equal(initialValue, gene.Value);
}
/*
Тест проверяет корректное изменение значения гена при срабатывании мутации,
когда новое значение вычисляется в пределах [xmin, xmax].
Для xmin=-5, xmax=5 нижняя граница смещения равна -1, верхняя – 1.
При втором значении rnd.NextDouble() равном 1.0, смещение Δ = -1 + 1*(2) = 1.
Новое значение = 1.0 + 1 = 2.0. */
[Fact]
public void Mutation_ShouldMutateValueWithinBounds()
{
double initialValue = 1.0;
double xmin = -5.0;
double xmax = 5.0;
double mutationProbability = 0.1;
// Первое значение 0.05 (< 0.1) – мутация срабатывает.
// Второе значение 1.0 приводит к Δ = -1 + 1.0*2 = 1.
var fakeRandom = new FakeRandom(new List<double> { 0.05, 1.0 });
Gen gene = new Gen(initialValue);
gene.Mutation(xmin, xmax, mutationProbability, fakeRandom);
// новое значение должно быть равно 2.0
Assert.Equal(2.0, gene.Value);
}
/*
Тест проверяет округление по верхнему пределу.
Если значение после мутации выходит за xmax, оно должно быть ограничено значением xmax.
Например, для initialValue=4.8 и Δ=1.0 новое значение 5.8 должно быть обрезано до 5.0.
*/
[Fact]
public void Mutation_ShouldClampToXmax_WhenValueExceedsXmax()
{
double initialValue = 4.8;
double xmin = -5.0;
double xmax = 5.0;
double mutationProbability = 0.1;
// Первое значение 0.05 (< 0.1) – мутация срабатывает.
// Второе значение 1.0 даёт Δ = 1, таким образом newValue = 4.8 + 1 = 5.8, которое должно быть округлено до 5.0.
var fakeRandom = new FakeRandom(new List<double> { 0.05, 1.0 });
Gen gene = new Gen(initialValue);
gene.Mutation(xmin, xmax, mutationProbability, fakeRandom);
// значение должно быть ограничено xmax (5.0)
Assert.Equal(5.0, gene.Value);
}
/*
Тест проверяет округление по нижнему пределу.
Если значение после мутации выходит за xmin, оно должно быть ограничено значением xmin.
Например, для initialValue=-4.8 и Δ=-1.0 новое значение -5.8 должно быть округлено до -5.0.
*/
[Fact]
public void Mutation_ShouldClampToXmin_WhenValueFallsBelowXmin()
{
double initialValue = -4.8;
double xmin = -5.0;
double xmax = 5.0;
double mutationProbability = 0.1;
// Первое значение 0.05 (< 0.1) – мутация срабатывает.
// Второе значение 0.0 приводит к Δ = xmin/5 = -1, таким образом newValue = -4.8 - 1 = -5.8, которое должно быть округлено до -5.0.
var fakeRandom = new FakeRandom(new List<double> { 0.05, 0.0 });
Gen gene = new Gen(initialValue);
gene.Mutation(xmin, xmax, mutationProbability, fakeRandom);
// значение должно быть ограничено xmin (-5.0)
Assert.Equal(-5.0, gene.Value);
}
}
}
3.4. Итог юнит-тестов оператора мутации
Рис.3 — Скриншот прохождения юнит-тестов оператора мутации
4. Оператор кроссинговера
4.1. Модель оператора кроссинговера
Оператор кроссинговера BLX‑a‑b.
Для i-го гена вычисляется d = |x - y|. Если x ≤ y, интервал для потомка [x - a*d, y + b*d], иначе [y - b*d, x + a*d]. Для каждого потомка генерируется случайное значение в этом интервале.
4.2. Код программной реализации оператора кроссинговера
namespace EvolutionaryAlgorithmTemplate
{
/*Класс Chromosome (Хромосома)
Хранит вектор рабочих параметров – список объектов Gen, каждый из которых содержит вещественное значение
Свойства:
Gens – список генов
XMin, XMax – границы допустимых значений рабочих параметров
Dimension – число рабочих параметров
Методы:
Конструкторы для создания хромосомы из списка вещественных значений или из списка генов
BLX‑a‑b кроссинговер – для создания потомков
Mutation – выполняет мутацию, выбирая случайный ген */
public class Chromosome
{
public List<Gen> Gens { get; private set; }
public double XMin { get; private set; }
public double XMax { get; private set; }
public int Dimension => Gens.Count;
// Создание хромосомы по списку рабочих параметров
public Chromosome(List<double> values, double xmin, double xmax)
{
XMin = xmin;
XMax = xmax;
Gens = new List<Gen>();
foreach (var val in values)
{
// Если значение выходит за пределы, ограничиваем его
double clamped = Math.Max(xmin, Math.Min(val, xmax));
Gens.Add(new Gen(clamped));
}
}
// Альтернативный конструктор, принимающий уже сформированные гены
public Chromosome(List<Gen> gens, double xmin, double xmax)
{
XMin = xmin;
XMax = xmax;
Gens = new List<Gen>(gens);
}
/*
Оператор кроссинговера BLX‑a‑b.
Для i-го гена вычисляется d = |x - y|. Если x ≤ y, интервал для потомка [x - a*d, y + b*d],
иначе [y - b*d, x + a*d]. Для каждого потомка генерируется случайное значение в этом интервале.
*/
public static (Chromosome, Chromosome) BLXabCrossover(
Chromosome parent1,
Chromosome parent2,
double a, double b,
double xmin, double xmax,
Random rnd)
{
if (parent1.Dimension != parent2.Dimension)
throw new ArgumentException("Размерности хромосом должны совпадать!");
int n = parent1.Dimension;
List<double> child1Values = new List<double>();
List<double> child2Values = new List<double>();
for (int i = 0; i < n; i++)
{
double x = parent1.Gens[i].Value;
double y = parent2.Gens[i].Value;
double d = Math.Abs(x - y);
double lower, upper;
if (x <= y)
{
lower = x - a * d;
upper = y + b * d;
}
else
{
lower = y - b * d;
upper = x + a * d;
}
double child1Value = lower + rnd.NextDouble() * (upper - lower);
double child2Value = lower + rnd.NextDouble() * (upper - lower);
// Ограничиваем значения в [xmin, xmax]
child1Value = Math.Max(xmin, Math.Min(child1Value, xmax));
child2Value = Math.Max(xmin, Math.Min(child2Value, xmax));
child1Values.Add(child1Value);
child2Values.Add(child2Value);
}
Chromosome child1 = new Chromosome(child1Values, xmin, xmax);
Chromosome child2 = new Chromosome(child2Values, xmin, xmax);
return (child1, child2);
}
// Выполняет мутацию хромосомы: выбирается случайный ген и к нему применяется метод мутации
public void Mutation(double mutationProbability, Random rnd)
{
int index = rnd.Next(Dimension);
Gens[index].Mutation(XMin, XMax, mutationProbability, rnd);
}
}
}
