/// <summary> /// Решение задачи линейного программирования /// </summary> /// <param name="minimax"></param> /// <param name="optimalVectors"></param> /// <param name="optimalValues"></param> /// <param name="trace"></param> /// <returns></returns> public bool Execute(ILinearMiniMax <T> minimax, ref IEnumerable <Vector <T> > optimalVectors, ref IEnumerable <T> optimalValues, ITrace trace) { // Реализуем алгоритм только для случая наращивания базиса по одной переменной Debug.Assert(H == 1); Debug.Assert(minimax.A.Rows == minimax.R.Count()); Debug.Assert(minimax.A.Rows == minimax.B.Count()); Debug.Assert(minimax.A.Columns == minimax.C.Count()); AppendLineCallback appendLineCallback = trace != null ? trace.AppendLineCallback : null; ProgressCallback progressCallback = trace != null ? trace.ProgressCallback : null; CompliteCallback compliteCallback = trace != null ? trace.CompliteCallback : null; if (progressCallback != null) { progressCallback(0, minimax.C.Count); } // Количество переменных int n = minimax.C.Count; // Величине рекорда присваивается заведомо плохое решение double r = minimax.Target == Target.Maximum ? Double.MinValue : Double.MaxValue; BooleanEnumPlan optimal = null; // Исходным выбранным частичным планом считается тот, базис которого пуст for ( var stack = new StackListQueue <BooleanEnumPlan>( new BooleanEnumPlan( new BooleanVector(), new Vector <int>(), minimax)); stack.Any(); ) { BooleanEnumPlan element = stack.Pop(); Debug.WriteLine("element = {0}", element); // Генерируем все возможные соседние планы, связанные с введением в базис H переменных // и вычисляем их оценки if (appendLineCallback != null) { appendLineCallback( string.Format( "Генерируем все возможные соседние планы, связанные с введением в базис {0} переменных", H)); } Debug.WriteLine( "Генерируем все возможные соседние планы, связанные с введением в базис {0} переменных", H); var list = new StackListQueue <BooleanEnumPlan>(new[] { true, false }.Select(value => new BooleanEnumPlan(new BooleanVector(element.Vector) { value }, new Vector <int>(element.Indeces) { Enumerable.Range(0, n).Except(element.Indeces).First() }, minimax)) .Where(item => item.ArgBound.All(x => x >= 0.0))); stack.Push(list); if (appendLineCallback != null) { appendLineCallback(string.Format("Количество подмножеств = {0}", stack.Count)); } Debug.WriteLine("Количество подмножеств = {0}", stack.Count); for (; stack.Any();) { // На множестве планов выбрать планы с наибольшим числом введёных переменных // На выбраном множестве выбрать с наилучшей оценкой if (appendLineCallback != null) { appendLineCallback( "На множестве планов выбрать планы с наибольшим числом введёных переменных"); } if (appendLineCallback != null) { appendLineCallback("На выбраном множестве выбрать с наилучшей оценкой"); } Debug.WriteLine("На множестве планов выбрать планы с наибольшим числом введёных переменных"); Debug.WriteLine("На выбраном множестве выбрать с наилучшей оценкой"); int max1 = stack.Max(item => item.Indeces.Count); list = new StackListQueue <BooleanEnumPlan>(stack.Where(item => item.Indeces.Count == max1)); double maxMax = list.Max(item => item.FuncMax); double minMin = list.Min(item => item.FuncMin); element = minimax.Target == Target.Maximum ? list.First(item => item.FuncMax == maxMax) : list.First(item => item.FuncMin == minMin); stack.Remove(element); Debug.WriteLine("element = {0}", element); if (minimax.Target == Target.Maximum && r <= maxMax || minimax.Target == Target.Minimum && r >= minMin) { // Если оценка элемента лучше рекорда if (appendLineCallback != null) { appendLineCallback("Оценка элемента лучше рекорда"); } if (element.Indeces.Count == n) { // Если в базис введены все элементы if (appendLineCallback != null) { appendLineCallback("В базис введены все элементы"); } // Запоминаем в рекорд оценку (т.е. значение для этого вектора) // и запоминаем этот план if (appendLineCallback != null) { appendLineCallback("Запоминаем в рекорд оценку"); } r = minimax.Target == Target.Maximum ? element.FuncMax : element.FuncMin; optimal = new BooleanEnumPlan(element); } else { // Иначе переходим к генерации соседних планов stack.Push(element); break; } } // Выводим из базиса H последних переменных, отбросив остальные планы с таким же числом переменных if (appendLineCallback != null) { appendLineCallback( string.Format( "Выводим из базиса {0} последних переменных, отбросив остальные планы с таким же числом переменных", H)); } element = new BooleanEnumPlan( new BooleanVector(element.Vector.GetRange(0, element.Vector.Count - 1)), new Vector <int>(element.Indeces.GetRange(0, element.Indeces.Count - 1)), minimax); stack.RemoveAll(item => item.Indeces.Count == element.Indeces.Count); Debug.WriteLine("element = {0}", element); if (appendLineCallback != null) { appendLineCallback(string.Format("Количество подмножеств = {0}", stack.Count)); } Debug.WriteLine("Количество подмножеств = {0}", stack.Count); if (element.Indeces.Count == 0) { break; } } } // Завершаем алгоритм и возвращаем найденное решение if (optimal == null) { return(false); } optimalVectors = new StackListQueue <Vector <T> >( new Vector <T>(optimal.Indeces.Select(index => optimal.Vector[index] ? (T)(dynamic)1 : default(T)))); optimalValues = new StackListQueue <T>((T)(dynamic)r); if (compliteCallback != null) { compliteCallback(); } return(true); }
public bool Execute(ILinearMiniMax <T> minimax, ref IEnumerable <Vector <T> > optimalVectors, ref IEnumerable <T> optimalValues, ITrace trace) { Debug.Assert(minimax.A.Rows == minimax.R.Count()); Debug.Assert(minimax.A.Rows == minimax.B.Count()); Debug.Assert(minimax.A.Columns == minimax.C.Count()); AppendLineCallback appendLineCallback = trace != null ? trace.AppendLineCallback : null; ProgressCallback progressCallback = trace != null ? trace.ProgressCallback : null; CompliteCallback compliteCallback = trace != null ? trace.CompliteCallback : null; // Количество переменных int n = minimax.C.Count; if (progressCallback != null) { progressCallback(0, minimax.C.Count); } // Исходным выбранным частичным планом считается тот, базис которого пуст var list = new StackListQueue <BooleanTreePlan>(new BooleanTreePlan( new BooleanVector(), minimax)); for (int k = 0;; k++) { if (appendLineCallback != null) { appendLineCallback(string.Format("Количество подмножеств = {0}", list.Count)); } if (progressCallback != null) { progressCallback(k, minimax.C.Count); } // Удаляем подмножества с заведомо недопустимыми значениями if (appendLineCallback != null) { appendLineCallback("Удаляем подмножества с заведомо недопустимыми значениями"); } Debug.WriteLine("Удаляем подмножества с заведомо недопустимыми значениями"); list.RemoveAll(item => item.ArgBound.Any(x => x < 0)); if (appendLineCallback != null) { appendLineCallback(string.Format("Количество подмножеств = {0}", list.Count)); } Debug.WriteLine("Количество подмножеств = {0}", list.Count); if (list.Count == 0) { return(false); } for (;;) { double maxMin = list.Max(item => item.FuncMin); double minMax = list.Min(item => item.FuncMax); if (appendLineCallback != null) { appendLineCallback(string.Format("maxMin = {0} minMax = {1}", maxMin, minMax)); } Debug.WriteLine("maxMin = {0} minMax = {1}", maxMin, minMax); if (maxMin <= minMax) { break; } // Удаляем варианты подмножеств, для которых существует более лучшая оценка целевой функции if (appendLineCallback != null) { appendLineCallback( "Удаляем варианты подмножеств, для которых существует более лучшая оценка целевой функции"); } switch (minimax.Target) { case Target.Maximum: list.RemoveAll(item => item.FuncMax < maxMin); break; case Target.Minimum: list.RemoveAll(item => item.FuncMin > minMax); break; } if (appendLineCallback != null) { appendLineCallback(string.Format("Количество подмножеств = {0}", list.Count)); } Debug.WriteLine("Количество подмножеств = {0}", list.Count); } if (k == n) { break; } // Шаг деления множеств пополам (дописыванием нуля и единицы) if (appendLineCallback != null) { appendLineCallback("Шаг деления множеств пополам (дописыванием нуля и единицы)"); } var next = new StackListQueue <BooleanTreePlan> { list.Select(item => new BooleanTreePlan(new BooleanVector(item.Vector) { false }, minimax)), list.Select(item => new BooleanTreePlan(new BooleanVector(item.Vector) { true }, minimax)) }; list = next; } // Завершаем алгоритм и возвращаем найденное решение optimalVectors = new StackListQueue <Vector <T> >( list.Select(item => new Vector <T>(item.Vector.Select(b => b ? (T)(dynamic)1 : default(T))))); optimalValues = new StackListQueue <T>(list.Select(item => (T)(dynamic)(item.FuncMin))); Debug.Assert(list.All(item => item.FuncMin <= item.FuncMax)); if (compliteCallback != null) { compliteCallback(); } return(true); }
public bool Execute(ILinearMiniMax <T> minimax, ref IEnumerable <Vector <T> > optimalVectors, ref IEnumerable <T> optimalValues, ITrace trace) { // Реализуем алгоритм только для случая наращивания базиса по одной переменной Debug.Assert(H == 1); Debug.Assert(minimax.A.Rows == minimax.R.Count()); Debug.Assert(minimax.A.Rows == minimax.B.Count()); Debug.Assert(minimax.A.Columns == minimax.C.Count()); // Количество переменных int n = minimax.C.Count; AppendLineCallback appendLineCallback = trace != null ? trace.AppendLineCallback : null; ProgressCallback progressCallback = trace != null ? trace.ProgressCallback : null; CompliteCallback compliteCallback = trace != null ? trace.CompliteCallback : null; if (progressCallback != null) { progressCallback(0, minimax.C.Count); } // Исходным выбранным частичным планом считается тот, базис которого пуст var list = new StackListQueue <BooleanMultiPlan>(new[] { true, false } .SelectMany(value => Enumerable.Range(0, n).Select( index => new BooleanMultiPlan(new BooleanVector(value), new Vector <int>(index), minimax)) .Where(item => item.ArgBound.All(x => x >= 0.0)))); for (int k = 1;; k++) { if (appendLineCallback != null) { appendLineCallback(string.Format("Количество подмножеств = {0}", list.Count)); } if (progressCallback != null) { progressCallback(k, minimax.C.Count); } // Удаляем подмножества с заведомо недопустимыми значениями if (appendLineCallback != null) { appendLineCallback("Удаляем подмножества с заведомо недопустимыми значениями"); } Debug.WriteLine("Удаляем подмножества с заведомо недопустимыми значениями"); list.RemoveAll(item => item.ArgBound.Any(x => x < 0)); if (appendLineCallback != null) { appendLineCallback(string.Format("Количество подмножеств = {0}", list.Count)); } Debug.WriteLine("Количество подмножеств = {0}", list.Count); if (list.Count == 0) { return(false); } for (;;) { double maxMin = list.Max(item => item.FuncMin); double minMax = list.Min(item => item.FuncMax); if (appendLineCallback != null) { appendLineCallback(string.Format("maxMin = {0} minMax = {1}", maxMin, minMax)); } Debug.WriteLine("maxMin = {0} minMax = {1}", maxMin, minMax); if (maxMin <= minMax) { break; } // Удаляем варианты подмножеств, для которых существует более лучшая оценка целевой функции if (appendLineCallback != null) { appendLineCallback( "Удаляем варианты подмножеств, для которых существует более лучшая оценка целевой функции"); } switch (minimax.Target) { case Target.Maximum: list.RemoveAll(item => item.FuncMax < maxMin); break; case Target.Minimum: list.RemoveAll(item => item.FuncMin > minMax); break; } if (appendLineCallback != null) { appendLineCallback(string.Format("Количество подмножеств = {0}", list.Count)); } Debug.WriteLine("Количество подмножеств = {0}", list.Count); } if (k == n) { break; } // Генерируем все возможные соседние планы, связанные с введением в базис H переменных // и вычисляем их оценки if (appendLineCallback != null) { appendLineCallback( string.Format( "Генерируем все возможные соседние планы, связанные с введением в базис {0} переменных", H)); } Debug.WriteLine( "Генерируем все возможные соседние планы, связанные с введением в базис {0} переменных", H); var next = new SortedStackListQueue <BooleanMultiPlan>(new[] { true, false } .SelectMany(value => list.Select( element => new BooleanMultiPlan(new BooleanVector(element.Vector) { value }, new Vector <int>(element.Indeces) { (element.Indeces.Last() + 1) % n }, minimax)) .Where(item => item.ArgBound.All(x => x >= 0.0)))) { Comparer = new BooleanMultiPlanComparer() }; if (appendLineCallback != null) { appendLineCallback("Удаляем дупликаты"); } Debug.WriteLine("Удаляем дупликаты"); list = new StackListQueue <BooleanMultiPlan>(next.Distinct()); Debug.WriteLine("Количество подмножеств = {0}", list.Count); } // Завершаем алгоритм и возвращаем найденное решение optimalVectors = new StackListQueue <Vector <T> >( list.Select(item => new Vector <T>( Enumerable.Range(0, n) .Select(index => item.Vector[item.Indeces.IndexOf(index)] ? (T)(dynamic)1 : default(T))))); optimalValues = new StackListQueue <T>(list.Select(item => (T)(dynamic)(item.FuncMin))); Debug.Assert(list.All(item => item.FuncMin <= item.FuncMax)); if (compliteCallback != null) { compliteCallback(); } return(true); }
/// <param name="a">Параметр алгоритма для вычисления весовых коэффициентов</param> /// <param name="relax"> /// При использовании метода релаксации задействовано в два раза меньше памяти и вычисления производятся /// на-месте. Для устанения коллизий с совместным доступом производится раскраска точек красное-чёрное для обработки /// их по-очереди /// </param> /// <param name="epsilon">Точность вычислений</param> /// <param name="gridSize"></param> /// <param name="blockSize"></param> /// <param name="trace"></param> public static IEnumerable <double> ExecuteLaplaceSolver(double epsilon, double a, bool relax, int gridSize = 0, int blockSize = 0, ITrace trace = null) { AppendLineCallback appendLineCallback = trace != null ? trace.AppendLineCallback : null; ProgressCallback progressCallback = trace != null ? trace.ProgressCallback : null; CompliteCallback compliteCallback = trace != null ? trace.CompliteCallback : null; if (gridSize > 0) { _gridSize3 = gridSize; _gridSize2 = blockSize; _gridSize1 = blockSize; } if (blockSize > 0) { _blockSize3 = blockSize; _blockSize2 = blockSize; _blockSize1 = blockSize; } Debug.Assert(_sizes.Length == _lengths.Length); Debug.Assert(_sizes.Aggregate(Int32.Mul) > 0); Debug.Assert(_lengths.Aggregate(Double.Mul) > 0.0); if (appendLineCallback != null) { appendLineCallback("Размер массива:"); } for (int i = 0; i < _sizes.Length; i++) { if (appendLineCallback != null) { appendLineCallback(string.Format("Размер массива по оси № {0}:\t{1}", i, _sizes[i])); } } // Степень дифференциального оператора // Реализовано только для оператора Лапласа // Для больших степеней надо использовать соответствующие полиномы большей степени // Для дифференциального оператора степени 2 (оператора Лапласа) полином имеет степень 1 // Для дифференциального оператора степени rank полином имеет степень rank-1 const int rank = 2; _extV = new int[_sizes.Length + 1]; _intV = new int[_sizes.Length + 1]; _intV[0] = _extV[0] = 1; for (int i = 1; i <= _sizes.Length; i++) { _extV[i] = _extV[i - 1] * _sizes[i - 1]; _intV[i] = _intV[i - 1] * (_sizes[i - 1] - rank); } // Расчёт коэффициентов слагаемых if (appendLineCallback != null) { appendLineCallback("Расчёт коэффициентов слагаемых"); } _w = new double[_sizes.Length + 1]; double sum2 = 0; for (int i = 0; i < _sizes.Length; i++) { sum2 += (_sizes[i] - 1) * (_sizes[i] - 1) / (_lengths[i] * _lengths[i]); } for (int i = 0; i < _sizes.Length; i++) { _w[i] = (_sizes[i] - 1) * (_sizes[i] - 1) / (_lengths[i] * _lengths[i]) / sum2 / (1.0 + a); } _w[_sizes.Length] = (a - 1.0) / (1.0 + a); if (appendLineCallback != null) { appendLineCallback("Коэффициенты:"); } for (int i = 0; i < _sizes.Length; i++) { if (appendLineCallback != null) { appendLineCallback(string.Format("Коэффициенты по оси № {0} (у двух точек):\t{1}", i, _w[i])); } } if (appendLineCallback != null) { appendLineCallback(string.Format("Коэффициент у средней точки:\t{0}", _w[_sizes.Length])); } if (appendLineCallback != null && relax) { appendLineCallback("Используется релаксация"); } CudafyModule km = CudafyTranslator.Cudafy(); GPGPU gpu = CudafyHost.GetDevice(); gpu.LoadModule(km); if (appendLineCallback != null) { appendLineCallback("Выделяем видеоресурсы"); } // При использовании метода релаксации задействовано в два раза меньше памяти и вычисления производятся // на-месте. Для устанения коллизий с совместным доступом производится раскраска точек красное-чёрное для обработки // их по-очереди double[][] devA = relax ? new[] { gpu.Allocate <double>(_a.Length) } : new[] { gpu.Allocate <double>(_a.Length), gpu.Allocate <double>(_a.Length) }; double[][] devB = { gpu.Allocate <double>(_gridSize3 * _blockSize3), gpu.Allocate(_b) }; double[][] devC = { gpu.Allocate <double>(_gridSize3 * _blockSize3), gpu.Allocate(_c) }; int[] devSizes = gpu.Allocate(_sizes); int[][] devV = { gpu.Allocate(_extV), gpu.Allocate(_intV) }; double[] devW = gpu.Allocate(_w); if (appendLineCallback != null) { appendLineCallback("Копируем данные в видеопамять"); } gpu.CopyToDevice(_a, devA[0]); gpu.CopyToDevice(_sizes, devSizes); gpu.CopyToDevice(_extV, devV[0]); gpu.CopyToDevice(_intV, devV[1]); gpu.CopyToDevice(_w, devW); if (!relax) { if (appendLineCallback != null) { appendLineCallback("Дублируем массив в видеопамяти"); } gpu.Launch(_gridSize3, _blockSize3, "Copy", devA[0], devA[1]); } var queue = new StackListQueue <double>(); for (int step = 0;; step++) { //if (AppendLineCallback != null) AppendLineCallback(string.Format("Шаг итерации № {0}", step)); // Вычисляем среднее взвешенное соседних точек //if (AppendLineCallback != null) AppendLineCallback("Вычисляем среднее взвешенное соседних точек"); if (!relax) { gpu.Launch(_gridSize3, _blockSize3, "LaplaceSolver", devA[step & 1], devA[1 - (step & 1)], devSizes, devV[0], devV[1], devW, devB[0], devC[0]); } else { gpu.Launch(_gridSize3, _blockSize3, "Clear", devB[0]); gpu.Launch(_gridSize3, _blockSize3, "Clear", devC[0]); for (int p = 0; p < 2; p++) { gpu.Launch(_gridSize3, _blockSize3, "LaplaceSolverWithRelax", devA[0], devSizes, devV[0], devV[1], devW, devB[0], devC[0], p); } } // Суммируем амплитуды изменений, посчитанные в каждом процессе gpu.Launch(_gridSize1, _blockSize1, "Sum", devB[0], devB[1]); gpu.Launch(_gridSize1, _blockSize1, "Sum", devC[0], devC[1]); gpu.CopyFromDevice(devB[1], _b); gpu.CopyFromDevice(devC[1], _c); double deltaSum = _b[0]; double squareSum = _c[0]; //if (AppendLineCallback != null) // AppendLineCallback(string.Format("Амплитуда изменений = {0}/{1}", deltaSum, squareSum)); queue.Enqueue(deltaSum / squareSum); if (deltaSum > epsilon * squareSum) { continue; } if (appendLineCallback != null) { appendLineCallback(string.Format("Потребовалось {0} итераций", step + 1)); } // Если изменения меньше заданной величины, то возвращаем вычисленные значения if (appendLineCallback != null) { appendLineCallback("Копируем массив из видеопамяти в массив на компьютере"); } if (!relax) { gpu.CopyFromDevice(devA[1 - (step & 1)], _a); } else { gpu.CopyFromDevice(devA[0], _a); } break; } // free the memory allocated on the GPU if (appendLineCallback != null) { appendLineCallback("Освобождаем видеоресурсы"); } gpu.FreeAll(); return(queue); }
/// <summary> /// Применение алгоритма симлекс-метода для решения задачи линейного программирования /// </summary> /// <param name="tr"> /// Признак проверки данных в индексной строке или индексном столбце. /// Transform.ByColumns: /// все элементы индексной строки отрицательные или ноль - при минимизации /// все элементы индексной строки положительные или ноль - при максимизации /// Transform.ByRows: /// все элементы индексного столбца положительные или ноль /// Transform.ByBoth: /// все элементы индексной строки отрицательные или ноль - при минимизации /// все элементы индексной строки положительные или ноль - при максимизации /// все элементы индексного столбца положительные или ноль /// </param> /// <param name="target"> /// Признак решаемой задачи - поиск максимума или поиск минимума линейного функционала /// </param> public void SimplexMethod(Transform tr, Target target) { AppendLineCallback appendLineCallback = AppendLineCallback; ProgressCallback progressCallback = ProgressCallback; CompliteCallback compliteCallback = CompliteCallback; var rowsIndex = new StackListQueue <int>(RowsIndex); var columnsIndex = new StackListQueue <int>(ColumnsIndex); // Подготовка данных для шага симплекс метода for (;;) { // Выбор ведущих строки и столбца. if (appendLineCallback != null) { appendLineCallback("Выбор ведущих строки и столбца"); } Debug.WriteLine("Выбор ведущих строки и столбца"); // В качестве ведущего выберем столбец, соответствующий переменной, так как наибольший коэффициент по модулю. // Из отрицательных коэффициентов индексной строки выбирается наибольший по абсолютной величине. // Затем элементы столбца свободных членов симплексной таблицы делит на элементы того же знака ведущего столбца. // Если tr != Transform.ByBoth то ищется допустимое решение // Если tr == Transform.ByBoth то ищется допустимое решение и оптимальное решение var enumerable = from r in Enumerable.Range(1, rowsIndex.Count) from c in columnsIndex where Math.Abs(this[r][c]) > Tolerance where this[r][0] < -Tolerance && this[r][0] * this[r][c] > Tolerance || tr != Transform.ByRows where this[0][c] * (double)target > Tolerance && this[0][c] * this[r][c] * (double)target > Tolerance || tr != Transform.ByColumns select new { row = r, col = c, value = Math.Abs((tr == Transform.ByRows) ? this[0][c] / this[r][c] : this[r][0] / this[r][c]) }; Debug.WriteLine(enumerable.Count()); if (!enumerable.Any()) { break; } // В задаче минимизации вводимой переменной должно соответствовать наименьшее из указанных // отношений, а в задаче максимизации – отношение, наименьшее по абсолютной величине double min = enumerable.Min(p => p.value); var first = enumerable.First(p => Math.Abs(p.value - min) <= Tolerance); int col = first.col; int row = first.row; Debug.WriteLine(string.Join(",", rowsIndex.Select(x => x.ToString(CultureInfo.InvariantCulture)))); Debug.WriteLine(string.Join(",", columnsIndex.Select(x => x.ToString(CultureInfo.InvariantCulture)))); if (appendLineCallback != null) { appendLineCallback(string.Format("строка = {0} столбец = {1}", row, col)); } Debug.WriteLine("строка = {0} столбец = {1}", row, col); //Пересчет симплекс-таблицы. Выполняем преобразования симплексной таблицы методом Жордано-Гаусса if (appendLineCallback != null) { appendLineCallback( "Пересчет симплекс-таблицы. Выполняем преобразования симплексной таблицы методом Жордано-Гаусса"); } Debug.WriteLine( "Пересчет симплекс-таблицы. Выполняем преобразования симплексной таблицы методом Жордано-Гаусса"); GaussJordanStep(Matrix <double> .Transform.TransformByRows, row, col); columnsIndex[columnsIndex.IndexOf(col)] = rowsIndex[row - 1]; rowsIndex[row - 1] = col; RowsIndex = rowsIndex; ColumnsIndex = columnsIndex; if (appendLineCallback != null) { appendLineCallback("Текущий опорный план:"); } if (appendLineCallback != null) { appendLineCallback(ToString()); } Debug.WriteLine("Текущий опорный план:"); Debug.WriteLine(ToString()); } }
public bool Execute(ILinearMiniMax <T> minimax, ref IEnumerable <Vector <T> > optimalVectors, ref IEnumerable <T> optimalValues, ITrace trace) { Debug.Assert(minimax.A.Rows == minimax.R.Count()); Debug.Assert(minimax.A.Rows == minimax.B.Count()); Debug.Assert(minimax.A.Columns == minimax.C.Count()); int n = Math.Min(minimax.A.Columns, minimax.C.Count()); // Количество переменных в уравнении AppendLineCallback appendLineCallback = trace != null ? trace.AppendLineCallback : null; ProgressCallback progressCallback = trace != null ? trace.ProgressCallback : null; CompliteCallback compliteCallback = trace != null ? trace.CompliteCallback : null; // 1. Составление псевдоплана. // Систему ограничений исходной задачи приводят к системе неравенств смысла >=. if (appendLineCallback != null) { appendLineCallback("Составление псевдоплана"); } Debug.WriteLine("Составление псевдоплана"); var simplexMatrix = new SimplexMatrix { AppendLineCallback = appendLineCallback, CompliteCallback = compliteCallback, ProgressCallback = progressCallback, }; // 1. Решаем задачу симплексным методом без учета условия целочисленности. Если все компоненты оптимального плана // целые, то он является оптимальным и для задачи целочисленного программирования. Если обнаруживается неразрешимость // задачи, то и неразрешима задача целочисленного программирования. if (appendLineCallback != null) { appendLineCallback("Решается задача линейного программирования без учета целочисленности."); } Debug.WriteLine("Решается задача линейного программирования без учета целочисленности."); bool b = simplexMatrix.DoubleSimplexMethod(minimax); if (!b) { return(false); } if (appendLineCallback != null) { appendLineCallback("Текущий опорный план:"); } if (appendLineCallback != null) { appendLineCallback(simplexMatrix.ToString()); } Debug.WriteLine("Текущий опорный план:"); Debug.WriteLine(simplexMatrix.ToString()); // 2. Если среди компонент оптимального решения есть нецелые, то к ограничениям задачи добавляем новое ограничение, // обладающее следующими свойствами: // - оно должно быть линейным; // - должно отсекать найденный оптимальный нецелочисленный план; // - не должно отсекать ни одного целочисленного плана. // Для построения ограничения выбираем компоненту оптимального плана с наибольшей дробной частью и по соответствующей // этой компоненте k-й строке симплексной таблицы записываем ограничение Гомори. for (IndexColumnVector indexColumnVector = simplexMatrix.GetIndexColumnVector(); indexColumnVector.HasFraction(n, simplexMatrix.RowsIndex, simplexMatrix.ColumnsIndex); indexColumnVector = simplexMatrix.GetIndexColumnVector()) { // В полученном оптимальном плане переменная имеет дробную часть числа. if (appendLineCallback != null) { appendLineCallback("В полученном оптимальном плане переменная имеет дробную часть числа."); } Debug.WriteLine("В полученном оптимальном плане переменная имеет дробную часть числа."); if (appendLineCallback != null) { appendLineCallback( "Составляется дополнительное ограничение для переменной, которая в оптимальном плане имеет максимальное дробное значение, хотя должна быть целой."); } Debug.WriteLine( "Составляется дополнительное ограничение для переменной, которая в оптимальном плане имеет максимальное дробное значение, хотя должна быть целой."); // Метод Гомори. // Составляется дополнительное ограничение для переменной, // которая в оптимальном плане имеет максимальное дробное значение, хотя должна быть целой. // Находим переменную с максимальной дробной частью if (appendLineCallback != null) { appendLineCallback("Находим переменную с максимальной дробной частью"); } Debug.WriteLine("Находим переменную с максимальной дробной частью"); var rowsIndex1 = new StackListQueue <int>(simplexMatrix.RowsIndex); var withFraction = new StackListQueue <int>( Enumerable.Range(1, indexColumnVector.Count - 1) .Where( i => rowsIndex1[i - 1] <= n && Math.Abs(indexColumnVector[i] - Math.Round(indexColumnVector[i])) > Tolerance)); var fractions = new StackListQueue <double>( withFraction.Select(i => Math.Abs(indexColumnVector[i] - Math.Floor(indexColumnVector[i])))); int k = withFraction[fractions.IndexOf(fractions.Max())]; // 1-based variable index Debug.WriteLine(Math.Abs(indexColumnVector[k] - Math.Floor(indexColumnVector[k]))); // Дополнительное ограничение составляем по строке k. if (appendLineCallback != null) { appendLineCallback(string.Format("Дополнительное ограничение составляем по строке {0}", k)); } Debug.WriteLine("Дополнительное ограничение составляем по строке {0}", k); rowsIndex1.Add(simplexMatrix.Columns); var vector = new Vector <double>(simplexMatrix[k].Select(a => Math.Floor(a) - a)) { 1 }; foreach (var r in simplexMatrix) { r.Add(0); } simplexMatrix.Add(vector); simplexMatrix.RowsIndex = rowsIndex1; if (appendLineCallback != null) { appendLineCallback("Текущий опорный план:"); } if (appendLineCallback != null) { appendLineCallback(simplexMatrix.ToString()); } Debug.WriteLine("Текущий опорный план:"); Debug.WriteLine(simplexMatrix.ToString()); // Transform.ByColumns: // все элементы индексной строки отрицательные или ноль - при минимизации // все элементы индексной строки положительные или ноль - при максимизации // Transform.ByRows: // все элементы индексного столбца положительные или ноль // Ищем допустимое и оптимальное решение simplexMatrix.SimplexMethod(SimplexMatrix.Transform.ByRows, minimax.Target); if (appendLineCallback != null) { appendLineCallback("Текущий опорный план:"); } if (appendLineCallback != null) { appendLineCallback(simplexMatrix.ToString()); } Debug.WriteLine("Текущий опорный план:"); Debug.WriteLine(simplexMatrix.ToString()); } double value = simplexMatrix[0][0]; // Завершаем алгоритм и возвращаем найденное решение if (appendLineCallback != null) { appendLineCallback("Завершаем алгоритм и возвращаем найденное решение"); } Debug.WriteLine("Завершаем алгоритм и возвращаем найденное решение"); var rowsIndex = new StackListQueue <int>(simplexMatrix.RowsIndex); optimalVectors = new StackListQueue <Vector <T> >( new Vector <T>( Enumerable.Range(1, n) .Select( i => (rowsIndex.Contains(i) ? (T)(dynamic)simplexMatrix[1 + rowsIndex.IndexOf(i)][0] : default(T))))); optimalValues = new StackListQueue <T>((T)(dynamic)value); if (compliteCallback != null) { compliteCallback(); } return(true); }
/// <summary> /// Рассмотрим следующую задачу линейного программирования: /// c^Tx -> max, Ax Le b, x Ge 0, b Ge 0. /// Любая общая задача ЛП может быть приведена к канонической форме. /// Приведение общей задачи ЛП к канонической форме достигается путем введения новых /// (их называют дополнительными) переменных. /// Двухфазный симплекс-метод /// Причины использования /// Если в условии задачи линейного программирования не все ограничения представлены неравенствами типа «≤», то далеко /// не всегда нулевой вектор будет допустимым решением. Однако каждая итерация симплекс-метода является переходом от /// одной вершины к другой, и если неизвестно ни одной вершины, алгоритм вообще не может быть начат. /// Процесс нахождения исходной вершины не сильно отличается от однофазного симплекс-метода, однако может в итоге /// оказаться сложнее, чем дальнейшая оптимизация. /// Модификация ограничений /// Все ограничения задачи модифицируются согласно следующим правилам: /// ограничения типа «≤» переводятся на равенства созданием дополнительной переменной с коэффициентом «+1». Эта /// модификация проводится и в однофазном симплекс-методе, дополнительные переменные в дальнейшем используются как /// исходный базис. /// ограничения типа «≥» дополняются одной переменной с коэффициентом «−1». Поскольку такая переменная из-за /// отрицательного коэффициента не может быть использована в исходном базисе, необходимо создать ещё одну, /// вспомогательную, переменную. Вспомогательные переменные всегда создаются с коэффициентом «+1». /// ограничения типа «=» дополняются одной вспомогательной переменной. /// Соответственно, будет создано некоторое количество дополнительных и вспомогательных переменных. В исходный базис /// выбираются дополнительные переменные с коэффициентом «+1» и все вспомогательные. Осторожно: решение, которому /// соответствует этот базис, не является допустимым. /// После того, как было модифицировано условие, создаётся вспомогательная целевая функция /// Если вспомогательные переменные были обозначены, как yi, i∈{1, .., k}, /// то вспомогательную функцию определим, как /// z' = \sum_{i=1}^k y_i -> min /// После этого проводится обыкновенный симплекс-метод относительно вспомогательной целевой функции. /// Поскольку все вспомогательные переменные увеличивают значение z', в ходе алгоритма /// они будут поочерёдно выводится из базиса, при этом после каждого перехода новое решение /// будет всё ближе к множеству допустимых решений. /// Когда будет найдено оптимальное значение вспомогательной целевой функции, могут возникнуть две ситуации: /// оптимальное значение z' больше нуля. Это значит, что как минимум одна из вспомогательных переменных осталась в /// базисе. /// В таком случае можно сделать вывод, что допустимых решений данной задачи линейного программирования не существует. /// оптимальное значение z' равно нулю. Это означает, что все вспомогательные переменные были выведены из базиса, /// и текущее решение является допустимым. /// Во втором случае мы имеем допустимый базис, или, иначе говоря, исходное допустимое решение. Можно проводить /// дальнейшую оптимизацию с учётом исходной целевой функции, при этом уже не обращая внимания на вспомогательные /// переменные. Это и является второй фазой решения. /// </summary> public bool DoubleSimplexMethod(ILinearMiniMax <T> minimax) { Debug.Assert(minimax.A.Rows == minimax.R.Count()); Debug.Assert(minimax.A.Rows == minimax.B.Count()); Debug.Assert(minimax.A.Columns == minimax.C.Count()); AppendLineCallback appendLineCallback = AppendLineCallback; ProgressCallback progressCallback = ProgressCallback; CompliteCallback compliteCallback = CompliteCallback; // количество исходныx переменныx // количество неравенств==количество дополнительных переменных // количество вспомогательных переменных int n = Math.Min(minimax.A.Columns, minimax.C.Count()); int count = Math.Min(minimax.A.Count(), minimax.B.Count()); int count1 = minimax.R.Count(r => r == Comparer.Ge); var rowsIndex = new StackListQueue <int>(Enumerable.Range(1 + n + count1, count + 1)); var columnsIndex = new StackListQueue <int>(Enumerable.Range(1, n + count1)); Debug.WriteLine("count = " + count); Debug.WriteLine((double)minimax.Target); // Модификация ограничений // Все ограничения задачи модифицируются согласно следующим правилам: // ограничения типа «≤» переводятся на равенства созданием дополнительной переменной с коэффициентом «+1». Эта // модификация проводится и в однофазном симплекс-методе, дополнительные переменные в дальнейшем используются как // исходный базис. // ограничения типа «≥» дополняются одной переменной с коэффициентом «−1». Поскольку такая переменная из-за // отрицательного коэффициента не может быть использована в исходном базисе, необходимо создать ещё одну, // вспомогательную, переменную. Вспомогательные переменные всегда создаются с коэффициентом «+1». // ограничения типа «=» дополняются одной вспомогательной переменной. // Соответственно, будет создано некоторое количество дополнительных и вспомогательных переменных. В исходный базис // выбираются дополнительные переменные с коэффициентом «+1» и все вспомогательные. Осторожно: решение, которому // соответствует этот базис, не является допустимым. //После того, как было модифицировано условие, создаётся вспомогательная целевая функция //Если вспомогательные переменные были обозначены, как yi, i∈{1, .., k}, //то вспомогательную функцию определим, как //z' = \sum_{i=1}^k y_i -> min //После этого проводится обыкновенный симплекс-метод относительно вспомогательной целевой функции. //Поскольку все вспомогательные переменные увеличивают значение z', в ходе алгоритма //они будут поочерёдно выводится из базиса, при этом после каждого перехода новое решение //будет всё ближе к множеству допустимых решений. //Когда будет найдено оптимальное значение вспомогательной целевой функции, могут возникнуть две ситуации: //оптимальное значение z' больше нуля. Это значит, что как минимум одна из вспомогательных переменных осталась в базисе. //В таком случае можно сделать вывод, что допустимых решений данной задачи линейного программирования не существует. //оптимальное значение z' равно нулю. Это означает, что все вспомогательные переменные были выведены из базиса, //и текущее решение является допустимым. var random = new Random(); var vector = new Vector <double>(0) { Enumerable.Repeat(0.0, n), Enumerable.Range(0, count1) .Select(x => 100000000000000.0 + 10000000000000.0 * random.NextDouble()), Enumerable.Repeat(0.0, count), 0, }; Add(vector); int i1 = 1 + n + count1; int i2 = 1 + n; for (int i = 0; i < count; i++) { var value = new Vector <double>(); switch (minimax.R.ElementAt(i)) { case Comparer.Le: // ограничения типа «≤» переводятся на равенства созданием дополнительной переменной с коэффициентом «+1» value.Add(Convert.ToDouble(minimax.B.ElementAt(i))); value.Add(minimax.A.ElementAt(i).Select(x => Convert.ToDouble(x))); value.Add(Enumerable.Repeat(0.0, count + count1 + 1)); value[i1++] = 1; break; case Comparer.Eq: // ограничения типа «=» дополняются одной вспомогательной переменной // Вспомогательные переменные всегда создаются с коэффициентом «+1» value.Add(Convert.ToDouble(minimax.B.ElementAt(i))); value.Add(minimax.A.ElementAt(i).Select(x => Convert.ToDouble(x))); value.Add(Enumerable.Repeat(0.0, count + count1 + 1)); value[i1++] = 1; break; case Comparer.Ge: // ограничения типа «≥» дополняются одной переменной с коэффициентом «−1» // Поскольку такая переменная из-за отрицательного коэффициента не может быть использована // в исходном базисе, необходимо создать ещё одну, вспомогательную, переменную // Вспомогательные переменные всегда создаются с коэффициентом «+1» value.Add(-Convert.ToDouble(minimax.B.ElementAt(i))); value.Add(minimax.A.ElementAt(i).Select(x => - Convert.ToDouble(x))); value.Add(Enumerable.Repeat(0.0, count + count1 + 1)); value[i1++] = 1; value[i2++] = 1; break; } Add(value); } Add(new Vector <double>(0) { minimax.C.Select(x => - Convert.ToDouble(x)), Enumerable.Repeat(0.0, count1), Enumerable.Repeat(0.0, count), 1, }); RowsIndex = rowsIndex; ColumnsIndex = columnsIndex; if (appendLineCallback != null) { appendLineCallback("Текущий опорный план:"); } if (appendLineCallback != null) { appendLineCallback(ToString()); } Debug.WriteLine("Текущий опорный план:"); Debug.WriteLine(ToString()); //После этого проводится обыкновенный симплекс-метод относительно вспомогательной целевой функции. //Поскольку все вспомогательные переменные увеличивают значение z', в ходе алгоритма //они будут поочерёдно выводится из базиса, при этом после каждого перехода новое решение //будет всё ближе к множеству допустимых решений. //Когда будет найдено оптимальное значение вспомогательной целевой функции, могут возникнуть две ситуации: //оптимальное значение z' больше нуля. Это значит, что как минимум одна из вспомогательных переменных осталась в базисе. //В таком случае можно сделать вывод, что допустимых решений данной задачи линейного программирования не существует. //оптимальное значение z' равно нулю. Это означает, что все вспомогательные переменные были выведены из базиса, //и текущее решение является допустимым. // Transform.ByColumns: // все элементы индексной строки отрицательные или ноль - при минимизации // Ищем допустимое решение (все элементы индексной строки отрицательные или ноль) SimplexMethod(Transform.ByColumns, Target.Minimum); Debug.WriteLine("Текущий опорный план:"); Debug.WriteLine(ToString()); if (this[0][0] > 0) { return(false); } this[0] = this[this.Count]; this.RemoveAt(this.Count - 1); rowsIndex = new StackListQueue <int>(RowsIndex); rowsIndex.Pop(); RowsIndex = rowsIndex; columnsIndex = new StackListQueue <int>(ColumnsIndex); ColumnsIndex = new StackListQueue <int>(columnsIndex.Except(Enumerable.Range(1 + n + count, count1 + 1))); Debug.WriteLine("Текущий опорный план:"); Debug.WriteLine(ToString()); // Ищется допустимое и оптимальное решение // (все элементы индексной строки отрицательные или ноль - при минимизации // все элементы индексной строки положительные или ноль - при максимизации // все элементы индексного столбца положительные или ноль) // Transform.ByColumns: // все элементы индексной строки отрицательные или ноль - при минимизации // все элементы индексной строки положительные или ноль - при максимизации // Transform.ByRows: // все элементы индексного столбца положительные или ноль // Ищем допустимое решение (все элементы индексной строки отрицательные или ноль) // Ищем допустимое и оптимальное решение SimplexMethod(Transform.ByColumns, minimax.Target); SimplexMethod(Transform.ByRows, minimax.Target); return(true); }