예제 #1
0
        /// <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);
        }
예제 #2
0
        /// <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();
        }
예제 #3
0
        /// <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);
        }
예제 #4
0
        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);
        }
예제 #5
0
        /// <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();
        }
예제 #6
0
        /// <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);
        }
예제 #7
0
        /// <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);
        }
예제 #8
0
        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);
            }
        }
예제 #9
0
        /// <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);
        }
예제 #10
0
        /// <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);
        }
예제 #11
0
        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();
        }
예제 #12
0
        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);
        }