// TODO: Think over reusing ParetoFinder from opt.Core /// <summary> /// Метод для проверки строгого доминирования первой особи /// над второй /// </summary> /// <param name="criteria">Список критериев оптимальности</param> /// <param name="firstIndividual">Первая особь</param> /// <param name="secondIndividual">Вторая особь</param> /// <returns>True, если первая особь строго доминирует над второй, иначе false</returns> public static bool CheckStrictDomination( Dictionary <TId, Criterion> criteria, NsgaIndividual firstIndividual, NsgaIndividual secondIndividual) { // По каждому критерию - надо проверить все foreach (Criterion crit in criteria.Values) { // Для удобства скопируем значения double firstIndividualValue = firstIndividual.Criteria[crit.Id]; double secondIndividualValue = secondIndividual.Criteria[crit.Id]; // Если при переходе от первой особи ко второй // мы смогли улучшить хоть один критерий, то первая // особь НЕ ДОМИНИРУЕТ над второй switch (crit.Type) { case CriterionType.Minimizing: if (firstIndividualValue > secondIndividualValue) { return(false); } break; case CriterionType.Maximizing: if (firstIndividualValue < secondIndividualValue) { return(false); } break; } } // Если при переходе от первой особи ко второй // мы не улучшили ни одного критерия, то первая // особь ДОМИНИРУЕТ над второй return(true); }
/// <summary> /// Метод для выполнения одноточечной инверсии особи /// </summary> /// <param name="population">Популяция, в которой находится родительская /// особь и в которую будет помещен потомок</param> /// <param name="unitNumber">Номер особи, над которой нужно выполнить инверсию /// (уникальный)</param> /// <param name="newGenerationNumber">Номер поколения, к которому относится потомок</param> private static void Inversion( ref Population <NsgaIndividual> population, int unitNumber, int newGenerationNumber) { // Признаки, у новой особи они будут такие же, // за исключением значений признаков - их поправим позже Dictionary <TId, IndividualAttribute> attributes = population[unitNumber].Attributes; // Генетический код родителя string parentChromo = population[unitNumber].GetChromo(); Random rnd = new Random(DateTime.Now.Millisecond + DateTime.Now.Second); // Точка инверсии // Такая формула обусловлена следующим: // - не должно получатся число, равное длине строки, потому что // нумерация символов zero-based и будет ошибка; int crossoverPointPosition = rnd.Next(parentChromo.Length - 1); // Разделим хромосому на части, первая из которых останется неизменной, // а вторая инвертируется string parentFirstPart = parentChromo.Substring(0, crossoverPointPosition); string parentSecondPart = parentChromo.Substring(crossoverPointPosition); // Инвертируем вторую часть хромосомы string newSecondPart = string.Empty; // Проверим каждый ген в этой части хромосомы char[] secondPart = parentSecondPart.ToCharArray(); foreach (char gene in secondPart) { // Выполним добавление гена к новой хромосоме, // попутно инвертировав его switch (gene) { case '1': newSecondPart += "0"; break; case '0': newSecondPart += "1"; break; } } // Составим хромосому потомка string сhildChromo = parentFirstPart + newSecondPart; // Получим и добавим в популяцию потомка var child = new NsgaIndividual( population.GetFreeUnitNumber(), newGenerationNumber, attributes); child.UpdateAttributes(сhildChromo); population.Add(child.Number, child); // Удалим из популяции исходную особь population.Remove(unitNumber); }
/// <summary> /// Метод для выполнения одноточечного скрещивания двух /// особей /// </summary> /// <param name="population">Популяция, в которой находятся две /// родительские особи и куда будут помещены два потомка</param> /// <param name="firstUnitNumber">Номер первого родителя в популяции (уникальный)</param> /// <param name="secondUnitNumber">Номер второго родителя в популяции (уникальный)</param> /// <param name="newGenerationNumber">Номер поколения, к которому относятся потомки</param> private static void CrossTwoUnits( ref Population <NsgaIndividual> population, int firstUnitNumber, int secondUnitNumber, int newGenerationNumber) { // Признаки, у новых двух особей они будут такие же, // за исключением значений признаков - их поправим позже var attributes = population[firstUnitNumber].Attributes; // Генетические коды родителей string firstChromo = population[firstUnitNumber].GetChromo(); string secondChromo = population[secondUnitNumber].GetChromo(); // Небольшая проверка: длины кодов должны быть одинаковые int chromoLength = firstChromo.Length; if (chromoLength != secondChromo.Length) { throw new Exception("Chromosome lengths of two parents must be the same"); } var rnd = new Random(DateTime.Now.Millisecond + DateTime.Now.Second); // Точка одноточечного скрещивания // Такая формула обусловлена следующим: // - не должно получатся число, равное длине строки, потому что // нумерация символов zero-based и будет ошибка; // - не должен получатся ноль, потому что тогда особи просто // обменяются кодами и ничего не выйдет int crossoverPointPosition = 1 + rnd.Next(chromoLength - 2); // Разделим хромосомы на части, из которых будут составлены // хромосомы потомков string firstParentFirstPart = firstChromo.Substring(0, crossoverPointPosition); string firstParentSecondPart = firstChromo.Substring(crossoverPointPosition); string secondParentFirstPart = secondChromo.Substring(0, crossoverPointPosition); string secondParentSecondPart = secondChromo.Substring(crossoverPointPosition); // Составим хромосомы потомков string firstChildChromo = firstParentFirstPart + secondParentSecondPart; string secondChildChromo = secondParentFirstPart + firstParentSecondPart; // Получим и добавим в популяцию первого потомка var firstChild = new NsgaIndividual( population.GetFreeUnitNumber(), newGenerationNumber, attributes); firstChild.UpdateAttributes(firstChildChromo); population.Add(firstChild.Number, firstChild); // Получим и добавим в популяцию второго потомка var secondChild = new NsgaIndividual( population.GetFreeUnitNumber(), newGenerationNumber, attributes); secondChild.UpdateAttributes(secondChildChromo); population.Add(secondChild.Number, secondChild); // Удалим из популяции родителей population.Remove(firstUnitNumber); population.Remove(secondUnitNumber); }
public static Population <NsgaIndividual> TournamentSelection( Population <NsgaIndividual> initPop, int selectionLimit) { // Сначала проверим: если надо отобрать больше, чем есть, // то выбросим ошибку if (selectionLimit > initPop.Count) { throw new ArgumentException("Number of units to be selected is greater than number of units in population"); } var selectedIndividuals = new Population <NsgaIndividual>(initPop.GetMaxUnitNumber()); // Если надо отобрать столько же, сколько есть, просто // вернем входную популяцию if (selectionLimit == initPop.Count) { foreach (NsgaIndividual individual in initPop) { selectedIndividuals.Add(individual.Number, (NsgaIndividual)individual.Clone()); } return(selectedIndividuals); } // Рандомайзер для выбора случайных особей Random rnd = new Random(DateTime.Now.Millisecond + DateTime.Now.Second); // Флагом того, что пора прекртатить итерации отбора, // будет служить совпадение количества отобранных // особей (в списке, объявленном выше) и переданного // в качестве аргумента требуемого количества int maxUnitNumber = initPop.GetMaxUnitNumber(); while (selectedIndividuals.Count != selectionLimit) { // В этом цикле будет проходить разбиение на пары // и выбор лучшей особи из пары // Список пары отобранных для турнира особей var selectedPair = new List <int>(); // Проверим: если среди неотобранных особей осталось всего две, // то составим из них пару и не будем морочить себе голову // случайностями if ((initPop.Count - selectedIndividuals.Count) == 2) { foreach (NsgaIndividual individual in initPop) { if (IsValidPairUnit(individual.Number, selectedPair, selectedIndividuals, initPop)) { selectedPair.Add(individual.Number); } } } // Флагом остановки выбора случайной особи в пару // будет служить наличие двух отобранных особей while (selectedPair.Count != 2) { // Будем выбирать случайное число в диапазоне // от 0 до значения счетчика особей в популяции // до тех пор, пока не наткнемся на особь, которую // можно отобрать в турнирную пару int rndNumber = -1; while (!IsValidPairUnit(rndNumber, selectedPair, selectedIndividuals, initPop)) { // "+ 1" из-за особенностей реализации метода // Random.Next(Int32, Int32). Если не будет "+ 1", // то maxUnitNumber не выпадет никогда rndNumber = rnd.Next(0, maxUnitNumber + 1); } // Когда найден подходящий номер, добавим его в пару selectedPair.Add(rndNumber); } // Выберем из пары лучшую особь // (согласно алгоритма NSGA-II) NsgaIndividual fittestIndividual = SelectFittestOfTwo(selectedPair, initPop); selectedIndividuals.Add(fittestIndividual.Number, (NsgaIndividual)fittestIndividual.Clone()); } // Вернем результат return(selectedIndividuals); }
/// <summary> /// Метод для преобразования модели в популяцию /// </summary> /// <param name="model">Исходная модель для преобразования</param> /// <param name="generationNumber">Номер поколения, который будет присвоен /// всем особям в создаваемой популяции</param> /// <returns></returns> private static Population <NsgaIndividual> ModelToPopulation(Model model, int generationNumber) { var population = new Population <NsgaIndividual>(); // 1. Сформируем словарь с параметрами особей. Все особи // данной популяции должны характеризоваться одинаковым // набором параметров, поэтому сформируем его заранее, а // потом будем передавать в качестве параметра конструктора // всем особям. Значения параметров будем затем назначать // вручную var attributes = new Dictionary <TId, IndividualAttribute>(); foreach (Parameter param in model.Parameters.Values) { var attribute = new IndividualAttribute( param.Id, param.MinValue, param.MaxValue, Program.ApplicationSettings.ValuesDecimalPlaces); attributes.Add(param.Id, attribute); } // Подготовим нормализованные критерии var normalizedExpCriteria = new Dictionary <TId, Dictionary <TId, double> >(); foreach (Experiment exp in model.Experiments.Values) { if (!exp.IsActive) { continue; } var critValues = new Dictionary <TId, double>(); normalizedExpCriteria.Add(exp.Id, critValues); } // Смена ингридиента: // Для каждого максимизируемого критерия выполним Y_new = 1 / Y_old // так он станет минимизируемым // Затем - нормализация var experiments = PrepareCriteria(model); // 2. Добавим в популяцию особи - столько же, сколько // АКТИВНЫХ экспериментов в модели foreach (Experiment exp in model.Experiments.Values) { if (!exp.IsActive) { continue; } var unit = new NsgaIndividual( exp.Number, generationNumber, attributes); // Запишем значения оптимизируемых параметров эксперимента // в словарь признаков особи foreach (Parameter param in model.Parameters.Values) { unit.Attributes[param.Id].Value = exp.ParameterValues[param.Id]; // Рассчитаем генетический код этой особи unit.Attributes[param.Id].ResolveCodeFromValue(); } // Запишем значения критериев оптимальности в словарь // критериев особи foreach (Criterion crit in model.Criteria.Values) { unit.Criteria.Add(crit.Id, experiments[exp.Id].CriterionValues[crit.Id]); } // Добавим особь в популяцию population.Add(exp.Number, unit); } return(population); }