def simplex_step(cf: CanonicalForm, xkN: np.array) -> Tuple[np.array, np.array, bool]: """ Performs one step of simplex algorithm. Parameters names corresponds to those in a book by Petuhov (Петухов) et al, p. 88. Algorithm is based on the same source. :param cf: parameters of problem in canonical form :param xkN: a starting vector: any support vector for a set defined by `cf` :return: 1. The next approximation - also a support vector (the value of target function is not increased) 2. Nk - a list of basis vectors of this support vector in A[M,N] 3. A boolean equal to True if iterations are to be stopped, otherwise False """ AMN, cN = cf.A, cf.c _, _, Nk0, Nk_plus = split_xkN( xkN) # Nk0 - indices of zero components of xk, Nk+ - positive binomGrid = binomial_grid( len(Nk0), cf.m - len(Nk_plus)) # auxiliary structure for building combinations that are # added to A[M,Nk+] for binom_idx in range(binomGrid[-1, -1]): # augment Nk+ to Nk so that A[M, Nk] is square AMNk, Nk, Lk = new_AMNk(AMN, xkN, binomGrid, binom_idx) # if determinant of the square matrix is 0, continue if np.linalg.det(AMNk) == 0: continue BNkM = calc_BNkM(AMNk) # calculating inverse matrix for A[M, Nk] cNk = np.array([cN[i] for i in Nk]) ykM = np.matmul(BNkM.T, cNk) dkN = cN - np.matmul(AMN.T, ykM) dkLk = np.array([dkN[int(i)] for i in Lk]) if np.min(dkLk) >= -1e-4: # xkN is already the optimal vector logging.info("solution found at iteration " + str(binom_idx + 1)) return xkN, Nk, True # index of first negative components in dkLk jk = Lk[list(filter(lambda j: dkLk[j] < -1e-4, range(len(Lk))))[0]] xkNk0, xkNk_plus, Nk0, Nk_plus = split_xkN(xkN) ukNk = np.matmul(BNkM, AMN[:, jk]) if np.max(ukNk) <= -1e-4: # target function is not lower bounded logging.info("solution does not exist") return np.array([np.inf for _ in range(cf.n)]), Nk, True if len(Nk_plus) == len(Nk) or max([ ukNk[i] for i in filter(lambda j: Nk[j] not in Nk_plus, range(len(Nk))) ]) < 0: ukN = [ ukNk[list(Nk).index(i)] if i in Nk else 0 for i in range(cf.n) ] ukN[jk] = -1 theta_k = min( [xkN[i] / ukN[i] for i in filter(lambda j: ukN[j] > 0, Nk)]) return xkN - np.multiply(theta_k, ukN), Nk, False logging.info("iterations ended with no result, something went wrong.") return xkN, np.array([]), True # something went wrong
def find_all_svs(cf: NpCanonicalForm): binom_table = binomial_grid(cf.n, cf.m) N = np.array(list(range(cf.n))) for idx in range(binom_table[-1, -1]): Nk = subset_by_index(N, binom_table, idx) AMNk = np.array([cf.A[:, i] for i in Nk]).T if np.abs(np.linalg.det(AMNk)) > 1e-3: xNk = np.matmul(np.linalg.inv(AMNk), cf.b) if np.min(xNk) < 0: continue xN = np.zeros(cf.n) for i in range(len(Nk)): xN[Nk[i]] = xNk[i] yield xN
def starting_vector(cf: CanonicalForm) -> np.array: binom_table = binomial_grid(cf.n, cf.m) N = np.array(list(range(cf.n))) for idx in range(binom_table[-1, -1]): Nk = subset_by_index(N, binom_table, idx) AMNk = np.array([cf.A[:, i] for i in Nk]).T if np.linalg.det(AMNk) != 0: xNk = np.matmul(np.linalg.inv(AMNk), cf.b) if np.min(xNk) < 0: continue # we need only vectors with all positive components xN = np.zeros(cf.n) for i in range(len(Nk)): xN[Nk[i]] = xNk[i] return xN logging.info("error, initial vector not found!") return np.zeros(cf.n)
def find_min_sv(cf: CanonicalForm, target_function: Callable[[np.array], float], abs_tolerance: float = 1e-3) -> BruteForceResult: binom_table = binomial_grid(cf.n, cf.m) N = np.array(list(range(cf.n))) min_sv, inv_AMNk_min, Nk_min = None, None, None for idx in range(binom_table[-1, -1]): Nk = subset_by_index(N, binom_table, idx) AMNk = np.array([cf.A[:, i] for i in Nk]).T if np.abs(np.linalg.det(AMNk)) > abs_tolerance: inv_AMNk = np.linalg.inv(AMNk) xNk = np.matmul(inv_AMNk, cf.b) if np.min(xNk) < 0: continue xN = np.zeros(cf.n) for i in range(len(Nk)): xN[Nk[i]] = xNk[i] if min_sv is None or target_function(xN) < target_function(min_sv): min_sv, inv_AMNk_min, Nk_min = xN, inv_AMNk, Nk return BruteForceResult(min_sv, inv_AMNk_min, Nk_min)
def simplex_step(cf: NpCanonicalForm, xkN: np.array) -> (np.array, np.array, bool): """ Совершает один шаг алгоритма симплекс-метода. Обозначения и процедура взяты из пособия Петухов и др., стр. 88 :param cf: параметры задачи, поставленной в канонической форме :param xkN: начальное приближение - некий опорный вектор к множеству, заданному `cf` :return: 1. следующее приближение - тоже опорный вектор, причём с ним значение целевой функции не возрастает 2. Nk - список индексов базисных векторов опорного вектора в матрице A[M,N] 3. булеву переменную, равную `true`, если итерирование нужно прекратить и `false` иначе """ AMN, cN = cf.A, cf.c _, _, Nk0, Nk_plus = split_xkN(xkN) # Nk0 - индексы нулевых компонент xk, Nk+ - положительных binomGrid = binomial_grid(len(Nk0), cf.m - len(Nk_plus)) # вспомог. структура для построения комбинаций столбцов, присоединяемых к A[M,Nk+] #for binom_idx in range(binomGrid[-1, -1] - 1, -1, -1): # итерируемся по комбинациям векторов, присоединяемых к A[M,Nk+] for binom_idx in range(binomGrid[-1, -1]): # В первом порядке алг-м слишком быстро находит решение нашей задачи AMNk, Nk, Lk = new_AMNk(AMN, xkN, binomGrid, binom_idx) # дополняем Nk+ до Nk так, что A[M, Nk] квадратная if np.linalg.det(AMNk) == 0: # если определитель построенной квадратной матрицы 0, пропускаем комбинацию continue BNkM = calc_BNkM(AMNk) # вычисление матрицы, обратной к A[M, Nk] cNk = np.array([cN[i] for i in Nk]) ykM = np.matmul(BNkM.T, cNk) dkN = cN - np.matmul(AMN.T, ykM) dkLk = np.array([dkN[int(i)] for i in Lk]) if np.min(dkLk) >= -1e-4: # xkN уже является оптимальным вектором print("solution found at iteration " + str(binom_idx + 1)) return xkN, Nk, True jk = Lk[list(filter(lambda j: dkLk[j] < -1e-4, range(len(Lk))))[0]] # индекс первой негативной компоненты в dkLk xkNk0, xkNk_plus, Nk0, Nk_plus = split_xkN(xkN) ukNk = np.matmul(BNkM, AMN[:, jk]) if np.max(ukNk) <= -1e-4: # целевая функция не ограничена снизу print("solution does not exist") return np.array([np.inf for _ in range(cf.n)]), Nk, True if len(Nk_plus) == len(Nk) or max([ukNk[i] for i in filter(lambda j: Nk[j] not in Nk_plus, range(len(Nk)))]) < 0: ukN = [ukNk[list(Nk).index(i)] if i in Nk else 0 for i in range(cf.n)] ukN[jk] = -1 theta_k = min([xkN[i]/ukN[i] for i in filter(lambda j: ukN[j] > 0, Nk)]) return xkN - np.multiply(theta_k, ukN), Nk, False print("iterations ended with no result, something went wrong.") return xkN, np.array([]), True # что-то пошло не так