Untitled
unknown
plain_text
9 months ago
11 kB
13
Indexable
import sympy as sp
import pandas as pd
def M_method_symbolic(A, b, c, maximize=True, M_large_value=10**6):
"""
M-метод (символічно з урахуванням M як "дуже великого" позитивного числа).
Параметри:
A, b, c - стандартні матриці/вектори (list або sympy-compatible)
maximize - True для максимізації, False для мінімізації
M_large_value - числова підстановка для порівнянь (fallback)
Повертає (x_vector, objective_value) або (None, None) при необмеженості.
"""
A = sp.Matrix(A)
b = sp.Matrix(b)
c = list(c) # зробимо список, будемо дописувати штучні коефіцієнти
m, n = A.shape
M = sp.Symbol('M', real=True, positive=True)
# === 1) Формуємо розширену матрицю A_aug і початковий базис B ===
A_aug = A.copy()
artificial_indices = [] # індекси доданих штучних змінних
B = [] # індекси базисних змінних, повинно вийти len(B) == m
for i in range(m):
unit_vector = [1 if k == i else 0 for k in range(m)]
found = None
for j in range(n):
if list(A[:, j]) == unit_vector:
found = j
break
if found is not None:
B.append(found)
else:
# додаємо штучну змінну (новий стовпець)
new_col = [0]*m
new_col[i] = 1
A_aug = A_aug.col_insert(A_aug.cols, sp.Matrix(new_col))
new_idx = A_aug.cols - 1
artificial_indices.append(new_idx)
B.append(new_idx)
c.append(-M) # штучна змінна має коеф -M
# якщо вхідний вектор c мав довжину n, ми вже дописали -M для штучних
n_new = A_aug.shape[1]
# захист: якщо c має менше елементів (наприклад, не дорівнює n), привести до n_new
if len(c) < n_new:
c = list(c) + [0]*(n_new - len(c))
c_new = sp.Matrix(c)
# Побудуємо початковий розв'язок x0: значення базисних змінних = b (за рядками)
x = sp.Matrix([0]*n_new)
for row_i in range(m):
basis_var_idx = B[row_i] # базисна змінна, що відповідає цьому рядку
x[basis_var_idx] = b[row_i]
N = [i for i in range(n_new) if i not in B]
print(f"Початкова база: {[f'x{i+1}' for i in B]}")
print(f"Початкова точка x0 = {x.T}")
# === Допоміжні функції для визначення знаку з урахуванням M ===
def sign_with_M(expr):
"""Повертає 1 якщо позитивне, -1 якщо негативне, 0 якщо нуль або невизначено (намагатись чисельно)."""
expr = sp.simplify(expr)
# якщо присутній M, дивимось коефіцієнт при M
if expr.has(M):
# коефіцієнт при M (припускаємо лінійність по M)
coeff = sp.expand(expr).coeff(M)
if coeff.is_positive is True:
return 1
if coeff.is_negative is True:
return -1
# невизначено за символікою — використовуємо числову підстановку
try:
val = float(sp.N(expr.subs(M, M_large_value)))
return 1 if val > 0 else (-1 if val < 0 else 0)
except Exception:
return 0
# якщо M не присутній
if expr.is_number:
try:
val = float(sp.N(expr))
return 1 if val > 0 else (-1 if val < 0 else 0)
except Exception:
return 0
if expr.is_positive is True:
return 1
if expr.is_negative is True:
return -1
# числова підстановка як останній fallback
try:
val = float(sp.N(expr))
return 1 if val > 0 else (-1 if val < 0 else 0)
except Exception:
return 0
def is_positive(expr):
return sign_with_M(expr) == 1
def is_negative(expr):
return sign_with_M(expr) == -1
step = 0
# === Основний цикл ===
while True:
step += 1
A_B = A_aug[:, B] # m x m (за умовою B має довжину m)
A_N = A_aug[:, N] # m x (n_new - m)
# c_B та c_N як вектори
c_B = sp.Matrix([c_new[i] for i in B])
c_N = sp.Matrix([c_new[i] for i in N])
# Обчислення потенціалів π: A_B^T * π = c_B
try:
pi = A_B.T.LUsolve(c_B)
except Exception:
# fallback на псевдообернену
pi = A_B.T.pinv() * c_B
# Δ = c_N^T - π^T * A_N
z_N = pi.T * A_N
delta = (c_N.T - z_N) # 1 x len(N)
# β = A_B^{-1} * b
try:
beta = A_B.LUsolve(b)
except Exception:
beta = A_B.pinv() * b
z_value = (c_B.T * beta)[0]
print("\n" + "="*70)
print(f"🔹 КРОК {step}")
print("="*70)
print(f"База: {[f'x{i+1}' for i in B]}")
print(f"π = {sp.simplify(pi.T)}")
print(f"Δ = {sp.simplify(delta)}")
print(f"β = {beta.T}")
print(f"f(x) = {sp.simplify(z_value)}")
# === Формуємо таблицю (A_B^{-1} * A_aug) у вигляді list-of-lists ===
try:
table_mat = A_B.inv() * A_aug
except Exception:
table_mat = A_B.pinv() * A_aug
table = table_mat.tolist() # m x n_new як звичайний nested list
# формуємо рядки DataFrame: [β] + ряд таблиці
df_rows = []
for i_row in range(m):
# безпечний доступ: якщо table має менше рядків — це помилка (але має бути m)
if i_row >= len(table):
raise IndexError("internal error: table has fewer rows than m (це не повинно відбуватися)")
df_rows.append([beta[i_row]] + table[i_row])
df = pd.DataFrame(
df_rows,
columns=["β"] + [f"x{i+1}" for i in range(n_new)],
index=[f"x{i+1}" for i in B]
)
print("\nПоточна таблиця:")
print(df)
# === Умова оптимальності ===
delta_list = [delta[0, j] for j in range(delta.shape[1])] if delta.shape[1] > 0 else []
if maximize:
enter_candidates = [j for j, val in enumerate(delta_list) if is_positive(val)]
else:
enter_candidates = [j for j, val in enumerate(delta_list) if is_negative(val)]
if not enter_candidates:
print("\n✅ Оптимум досягнуто.")
print(f"x* = {x.T}")
print(f"f(x*) = {sp.simplify(z_value)}")
return x, sp.simplify(z_value)
# вибираємо перший кандидат на вхід (можна змінити політику)
j_in = enter_candidates[0]
entering = N[j_in] # індекс змінної в повному наборі
# обчислення d: A_B * d = A_aug[:, entering] => d = A_B^{-1} * column
try:
d = A_B.LUsolve(A_aug[:, entering])
except Exception:
d = A_B.pinv() * A_aug[:, entering]
# перевірка необмеженості: якщо немає додатних елементів у d -> необмежений
if not any(sign_with_M(d[i]) == 1 for i in range(m)):
print("⚠️ Розв’язок не обмежений.")
return None, None
# обчислення θ: ratios тільки для d[i] > 0 (враховуємо символіку через sign_with_M)
ratios = [sp.oo] * m
ratios_eval = [float('inf')] * m
for i_row in range(m):
if sign_with_M(d[i_row]) == 1:
r = sp.simplify(beta[i_row] / d[i_row])
ratios[i_row] = r
try:
# числова апроксимація для порівняння — підставляємо велике M
r_eval = float(sp.N(r.subs(M, M_large_value)))
except Exception:
r_eval = float('inf')
ratios_eval[i_row] = r_eval
i_out = int(min(range(m), key=lambda i: ratios_eval[i]))
leaving = B[i_out]
print(f"\n➡️ Входить змінна x{entering+1}, виходить x{leaving+1}")
print(f"d = {d.T}")
print(f"θ (символічні) = {ratios}")
print(f"θ (числ. апрокс) = {ratios_eval}")
print(f"Мінімальне θ = {ratios[i_out]} (рядок {i_out})")
# Оновлення вектора x
theta = ratios[i_out]
for i_row in range(m):
x[B[i_row]] = sp.simplify(beta[i_row] - theta * d[i_row])
x[entering] = sp.simplify(theta)
x[leaving] = 0
# Оновлюємо базис
B[i_out] = entering
N = [i for i in range(n_new) if i not in B]
print(f"🔁 Новий базис: {[f'x{i+1}' for i in B]}")
print(f"x = {x.T}")
print(f"f(x) = {sp.simplify(c_new.dot(x))}")
# === ПРИКЛАД ===
if __name__ == "__main__":
A = [
[3, 1, 1, 1, 1],
[2, -1, 3, 0, 0],
[0, 5, 6, 1, 0]
]
b = [5, 4, 11]
c = [5, -1, -1, 0, 0]
print("=== АНАЛІТИЧНИЙ M-МЕТОД ===")
x_star, f_star = M_method_symbolic(A, b, c, maximize=False)
print("\nРЕЗУЛЬТАТ:")
print("x* =", x_star)
print("f(x*) =", f_star)
Editor is loading...
Leave a Comment