/// <summary> /// Метод для проверки сгенерированного номера особи /// на пригодность для включения в стек номеров особей /// </summary> /// <param name="numToCheck">Номер для проверки</param> /// <param name="selectedUnitNumbers">Стек с уже отобранными номерами</param> /// <param name="population">Популяция, в которой предполагается существование /// особи с таким номером</param> /// <returns>True, если особь: /// - имеет номер, больше либо равный нулю; /// - не была отобрана на предыдущих итерациях; /// - существует в популяции</returns> private static bool IsValidRandomUnit( int numToCheck, Stack <int> selectedUnitNumbers, AdditivePopulation population) { // Номер должен быть больше либо равен нулю if (numToCheck < 0) { return(false); } // Номер не должен быть уже отобран if (selectedUnitNumbers.Contains(numToCheck)) { return(false); } // Особь с таким номером должна существовать в популяции if (!population.ContainsUnit(numToCheck)) { return(false); } // Норма return(true); }
/// <summary> /// Метод для вывода информации о популяции в таблицу /// </summary> /// <param name="table">ПОДГОТОВЛЕННАЯ (PrepareProcessDataGrid) таблица</param> /// <param name="population">Популяция, информацию о которой нужно /// вывести в таблицу</param> public static void AddPopulationToDataGrid( DataGridView table, AdditivePopulation population, bool showDelimiterRow) { table.SuspendLayout(); // Предварительно вставим рядок-разделитель, если // так хочет вызывающий метод if (showDelimiterRow) { int delimiterRow = table.Rows.Add(); foreach (DataGridViewCell cell in table.Rows[delimiterRow].Cells) { cell.Value = "---"; } } // Переберем всю популяцию, отводя на особь по рядку foreach (AdditiveIndividual unit in population) { int unitRow = table.Rows.Add(); table.Rows[unitRow].Cells[0].Value = unit.Number.ToString(); table.Rows[unitRow].Cells[1].Value = unit.Generation.ToString(); table.Rows[unitRow].Cells[2].Value = unit.FitnessValue.ToString(SettingsManager.Instance.DoubleStringFormat); table.Rows[unitRow].Cells[3].Value = unit.GetChromo(); } table.ResumeLayout(); }
/// <summary> /// Метод для выполнения одноточечной инверсии особи /// </summary> /// <param name="population">Популяция, в которой находится родительская /// особь и в которую будет помещен потомок</param> /// <param name="unitNumber">Номер особи, над которой нужно выполнить инверсию /// (уникальный)</param> /// <param name="newGenerationNumber">Номер поколения, к которому относится потомок</param> private static void Inversion( ref AdditivePopulation 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; // Получим и добавим в популяцию потомка AdditiveIndividual child = new AdditiveIndividual( population.GetFreeUnitNumber(), newGenerationNumber, attributes); child.UpdateAttributes(сhildChromo); population.Add(child.Number, child); }
public static AdditivePopulation PerformMutation( AdditivePopulation initPop, double mutationProbability) { // Реализация мутации // Генератор случайных чисел Random rnd = new Random(DateTime.Now.Millisecond + DateTime.Now.Second); // В популяции переберем каждую особь foreach (AdditiveIndividual unit in initPop) { string newChromo = string.Empty; // Для данной особи проверим каждый ген в // хромосоме char[] unitChromo = unit.GetChromo().ToCharArray(); foreach (char gene in unitChromo) { double rndNum = rnd.NextDouble(); // Если выпало меньше, чем вероятность, // то мутация произошла, иначе - нет if (rndNum <= mutationProbability) { // Выполним добавление гена к новой хромосоме, // попутно инвертировав его switch (gene) { case '1': newChromo += "0"; break; case '0': newChromo += "1"; break; } } else { // Выполним добавление гена к новой хромосоме, // не инвертируя его newChromo += gene.ToString(); } } // Обновим данные об особи на основе новой // хромосомы unit.UpdateAttributes(newChromo); // Обнулим значение пригодности, потому что оно уже // не соответствует значению признаков unit.FitnessValue = double.NaN; } // Вернем измененную популяцию return(initPop); }
/// <summary> /// Метод для преобразования модели в популяцию /// </summary> /// <param name="model">Исходная модель для преобразования</param> /// <param name="generationNumber">Номер поколения, который будет присвоен /// всем особям в создаваемой популяции</param> /// <returns></returns> private static AdditivePopulation ModelToPopulation(Model model, int generationNumber) { AdditivePopulation pop = new AdditivePopulation(); // 1. Сформируем словарь с параметрами особей. Все особи // данной популяции должны характеризоваться одинаковым // набором параметров, поэтому сформируем его заранее, а // потом будем передавать в качестве параметра конструктора // всем особям. Значения параметров будем затем назначать // вручную Dictionary <TId, IndividualAttribute> attributes = new Dictionary <TId, IndividualAttribute>(); foreach (Parameter param in model.Parameters.Values) { IndividualAttribute attribute = new IndividualAttribute( param.Id, param.MinValue, param.MaxValue, Program.ApplicationSettings.ValuesDecimalPlaces); attributes.Add(param.Id, attribute); } // 2. Добавим в популяцию особи - столько же, сколько // АКТИВНЫХ экспериментов в модели foreach (Experiment exp in model.Experiments.Values) { if (exp.IsActive) { AdditiveIndividual unit = new AdditiveIndividual( 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(); } // Добавим особь в популяцию pop.Add(exp.Number, unit); } } return(pop); throw new NotImplementedException(); }
/// <summary> /// Метод для преобразования популяции в модель /// </summary> /// <param name="initModel">Изначальная модель, нужна для получения /// информации о параметрах, критериях и Ф.О., которая не хранится /// в популяции</param> /// <param name="population">Популяция, которую нужно превратить в модель</param> /// <returns>Модель, которая получена из популяции</returns> private static Model PopulationToModel(Model initModel, AdditivePopulation population) { Model result = new Model(); // Скопируем в новую модель информацию об оптимизируемых параметрах foreach (Parameter param in initModel.Parameters.Values) { result.Parameters.Add((Parameter)param.Clone()); } // Скопируем в новую модель информацию о критериях оптимальности foreach (Criterion crit in initModel.Criteria.Values) { result.Criteria.Add((Criterion)crit.Clone()); } // Скопируем в новую модель информацию о функциональных ограничениях foreach (Constraint constr in initModel.FunctionalConstraints.Values) { result.FunctionalConstraints.Add((Constraint)constr.Clone()); } // Скопируем в модель информацию об экспериментах // Сколько в популяции особей, столько будет в модели экспериментов foreach (AdditiveIndividual unit in population) { Experiment exp = new Experiment(unit.Number, unit.Number); // Значения параметров для данной особи foreach (Parameter param in result.Parameters.Values) { exp.ParameterValues.Add(param.Id, unit.Attributes[param.Id].Value); } // Добавим эксперимент в модель result.Experiments.Add(exp); } // Вернем модель return(result); }
/// <summary> /// Метод для получения результатов работы генетического алгоритма /// </summary> /// <param name="population">Популяция, на основе которой строится результат</param> /// <returns>Результат работы генетического алгоритма</returns> private static AdditiveMethodResult PrepareResult(AdditivePopulation population) { AdditiveMethodResult result = new AdditiveMethodResult("Генетический алгоритм", "Значение функции приспособленности"); // Отсортируем результаты по возрастанию по значению // аддитивного критерия (меньше - лучше) List <SortableDouble> sortedIndividuals = population.Select <AdditiveIndividual, SortableDouble>( ind => new SortableDouble() { Direction = SortDirection.Ascending, Id = ind.Number, Value = ind.FitnessValue } ).ToList(); sortedIndividuals.Sort(); // Заполним результаты foreach (SortableDouble sortedIndividual in sortedIndividuals) { result.SortedPoints.Add(sortedIndividual.Id); result.AdditionalData.Add(sortedIndividual.Id, sortedIndividual.Value); } return(result); }
public static AdditivePopulation OnePointCrossover( AdditivePopulation initPop, int newGenerationNumber) { // Для начала проверим валидность данных // Если в популяции ноль особей, то ошибка if (initPop.Count < 1) { throw new ArgumentException("Can not perform crossover on a population with 0 units in it"); } // Если номер нового (следующего) поколения меньше или равен // номеру текущего, то ошибка if (newGenerationNumber <= initPop.GetMaxGenerationNumber()) { throw new Exception("New generation number is lesser than current"); } // Все в порядке, можно проводить скрещивание // Генератор случайных чисел Random rnd = new Random(DateTime.Now.Millisecond + DateTime.Now.Second); // Для создания пар сформируем стек. // 1. Заполним его случайным образом (в случ. // порядке) всеми имеющимися особями. // 2. Будем доставать по две и скрещивать их между собой. // 3. Если осталось 0 особей, то конец. // 4. Если осталась 1 особь, то инверсия и goto п. 3. Stack <int> unitsStack = new Stack <int>(); int maxUnitNumber = initPop.GetMaxUnitNumber(); while (unitsStack.Count < initPop.Count) { // Выберем из популяции случайную особь int rndNumber = -1; while (!IsValidRandomUnit(rndNumber, unitsStack, initPop)) { // "+ 1" из-за особенностей реализации метода // Random.Next(Int32, Int32). Если не будет "+ 1", // то maxUnitNumber не выпадет никогда rndNumber = rnd.Next(0, maxUnitNumber + 1); } // Номер подходящий, добавим его в стек unitsStack.Push(rndNumber); } // Теперь стек заполнен номерами имеющихся особей в // случайном порядке // Будем доставать оттуда по две, пока там не останется // 0 или 1 особь, и скрещивать while (unitsStack.Count >= 2) { int firstParent = unitsStack.Pop(); int secondParent = unitsStack.Pop(); AdditiveCrossover.CrossTwoUnits( ref initPop, firstParent, secondParent, newGenerationNumber); } // Если осталось 0 особей, то всё готово, вернем // популяцию if (unitsStack.Count == 0) { return(initPop); } else { // Иначе же осталась 1 особь и мы должны выполнить инверсию AdditiveCrossover.Inversion( ref initPop, unitsStack.Pop(), newGenerationNumber); return(initPop); } }
/// <summary> /// Метод для выполнения одноточечного скрещивания двух /// особей /// </summary> /// <param name="population">Популяция, в которой находятся две /// родительские особи и куда будут помещены два потомка</param> /// <param name="firstUnitNumber">Номер первого родителя в популяции (уникальный)</param> /// <param name="secondUnitNumber">Номер второго родителя в популяции (уникальный)</param> /// <param name="newGenerationNumber">Номер поколения, к которому относятся потомки</param> private static void CrossTwoUnits( ref AdditivePopulation population, int firstUnitNumber, int secondUnitNumber, int newGenerationNumber) { // Признаки, у новых двух особей они будут такие же, // за исключением значений признаков - их поправим позже Dictionary <TId, IndividualAttribute> 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"); } Random 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; // Получим и добавим в популяцию первого потомка AdditiveIndividual firstChild = new AdditiveIndividual( population.GetFreeUnitNumber(), newGenerationNumber, attributes); firstChild.UpdateAttributes(firstChildChromo); population.Add(firstChild.Number, firstChild); // Получим и добавим в популяцию второго потомка AdditiveIndividual secondChild = new AdditiveIndividual( population.GetFreeUnitNumber(), newGenerationNumber, attributes); secondChild.UpdateAttributes(secondChildChromo); population.Add(secondChild.Number, secondChild); }
/// <summary> /// Метод для поиска решения с помощью генетического алгоритма /// </summary> /// <param name="model">Оптимизационная модель, эксперименты из которой /// выступят в качестве начальной популяции</param> /// <param name="gaParams">Параметры генетического алгоритма</param> /// <param name="table">Таблица для отображения процесса поиска решения. /// Если null, то процесс поиска решения не отображается</param> public static AdditiveMethodResult FindDecision(Model model, AdditiveParams gaParams, DataGridView table) { bool showProcess = (table != null); int currentGeneration = 0; // 1. Получим начальную популяцию из модели AdditivePopulation population = AdditiveSolver.ModelToPopulation(model, currentGeneration); // Рассчитаем значения функции приспособленности AdditiveSolver.CalcUnitsFitness( ref model, ref population, gaParams.ExternalAppPath); // Если надо, подготовим таблицу к выводу процесса и // выведем начальную популяцию (без разделителя) if (showProcess) { AdditiveDataGridFiller.PrepareProcessDataGrid(table); AdditiveDataGridFiller.AddPopulationToDataGrid(table, population, false); } // 2. Пока не достигнуто нужное количество поколений while (currentGeneration < gaParams.MaxGenerationsNumber) { // Увеличим счетчик поколений currentGeneration++; // 3. Отбор population = AdditiveSelection.TournamentSelection(population, gaParams.SelectionLimit); // 4. Скрещивание population = AdditiveCrossover.OnePointCrossover(population, currentGeneration); // 5. Мутация population = AdditiveMutation.PerformMutation(population, gaParams.MutationProbability); // 6. Расчет приспособленности // Модель обновится, метод CalcUnitsFitness рассчитает // для популяции значения функции приспособленности, а для // модели - значения критериев и Ф.О. model = AdditiveSolver.PopulationToModel(model, population); AdditiveSolver.CalcUnitsFitness( ref model, ref population, gaParams.ExternalAppPath); // Если надо, то выведем на экран информацию (с разделителем) if (showProcess) { AdditiveDataGridFiller.AddPopulationToDataGrid(table, population, true); } } // Подготовим результат на основе конечной популяции AdditiveMethodResult result = AdditiveSolver.PrepareResult(population); // Вернем результат return(result); }
private static void CalcUnitsFitness( ref Model initModel, ref AdditivePopulation population, string externalAppPath) { // У нас есть модель. Рассчитаем для нее с помощью внешней // программы значения критериев оптимальности и Ф.О. #if DUMMY // Запустим внешнюю программу и дождемся, пока она отработает if (!AdditiveSolver.UseDummyExternalApplication(initModel, externalAppPath)) { throw new Exception("Can not calculate fitness values using external program"); } #else // Файл для обмена данными между opt и расчетной программой string dataFilePath = System.IO.Path.GetDirectoryName(externalAppPath) + "\\_ga_temp_file.xml"; // Запустим внешнюю программу и дождемся, пока она отработает if (!AdditiveSolver.UseExternalApplication( initModel, externalAppPath, dataFilePath)) { throw new Exception("Can not calculate fitness values using external program"); } // Раз программа отработала, прочтем результаты из файла initModel = modelProvider.Load(dataFilePath); // Удалим файл с данными if (System.IO.File.Exists(dataFilePath)) { System.IO.File.Delete(dataFilePath); } #endif // Применим решатель для аддитивного критерия, чтобы получить его значения. // Они послужат нам эквивалентом функции приспособленности AdditiveCriterionSolver solver = new AdditiveCriterionSolver(); IntegralCriterionMethodResult result = solver.FindDecision(initModel); // Из результатов аддитивного критерия выдерем его значения foreach (TId expId in result.SortedPoints) { int unitNumber = initModel.Experiments[expId].Number; population[unitNumber].FitnessValue = result.AdditionalData[expId]; } // Применим Ф.О. к модели initModel.ApplyFunctionalConstraints(); // Пометим на удаление в популяции особи, соответсвующие экспериментам, // ставшим неактивными после применения Ф.О. foreach (Experiment exp in initModel.Experiments.Values) { if (!exp.IsActive) { population.MarkForRemoval(exp.Number); } } // И удалим помеченные population.RemoveMarked(); }
public static AdditivePopulation TournamentSelection( AdditivePopulation initPop, int selectionLimit) { // Сначала проверим: если надо отобрать больше, чем есть, // то выбросим ошибку if (selectionLimit > initPop.Count) { throw new ArgumentException("Number of units to be selected is greater than number of units in population"); } // Если надо отобрать столько же, сколько есть, просто // вернем входную популяцию if (selectionLimit == initPop.Count) { return(initPop); } // Список номеров отобранных особей List <int> selectedUnitNumbers = new List <int>(); // Рандомайзер для выбора случайных особей Random rnd = new Random(DateTime.Now.Millisecond + DateTime.Now.Second); // Флагом того, что пора прекртатить итерации отбора, // будет служить совпадение количества отобранных // особей (в списке, объявленном выше) и переданного // в качестве аргумента требуемого количества int maxUnitNumber = initPop.GetMaxUnitNumber(); while (selectedUnitNumbers.Count != selectionLimit) { // В этом цикле будет проходить разбиение на пары // и выбор лучшей особи из пары // Список пары отобранных для турнира особей List <int> selectedPair = new List <int>(); // Проверим: если среди неотобранных особей осталось всего две, // то составим из них пару и не будем морочить себе голову // случайностями if ((initPop.Count - selectedUnitNumbers.Count) == 2) { foreach (AdditiveIndividual unit in initPop) { if (IsValidPairUnit(unit.Number, selectedPair, selectedUnitNumbers, initPop)) { selectedPair.Add(unit.Number); } } } // Флагом остановки выбора случайной особи в пару // будет служить наличие двух отобранных особей while (selectedPair.Count != 2) { // Будем выбирать случайное число в диапазоне // от 0 до значения счетчика особей в популяции // до тех пор, пока не наткнемся на особь, которую // можно отобрать в турнирную пару int rndNumber = -1; while (!IsValidPairUnit(rndNumber, selectedPair, selectedUnitNumbers, initPop)) { // "+ 1" из-за особенностей реализации метода // Random.Next(Int32, Int32). Если не будет "+ 1", // то maxUnitNumber не выпадет никогда rndNumber = rnd.Next(0, maxUnitNumber + 1); } // Когда найден подходящий номер, добавим его в пару selectedPair.Add(rndNumber); } // Выберем из пары наиболее приспособленную особь // (ту, у которой значение функции приспособленности // лучше - то есть меньше, потому что мы работаем с // аддитивным критерием) if (opt.Helpers.Comparer.IsFirstValueBetter( initPop[selectedPair[0]].FitnessValue, initPop[selectedPair[1]].FitnessValue, opt.DataModel.CriterionType.Minimizing)) { // Первая особь лучше второй selectedUnitNumbers.Add(selectedPair[0]); } else { // Вторая особь лучше первой selectedUnitNumbers.Add(selectedPair[1]); } } // Пометим на удаление все особи, кроме отобранных во время турнира foreach (AdditiveIndividual unit in initPop) { if (!selectedUnitNumbers.Contains(unit.Number)) { initPop.MarkForRemoval(unit.Number); } } // Удалим помеченные особи initPop.RemoveMarked(); // Вернем результат return(initPop); }