Simplex 표 계산

OR
학부 정리
공개

2025년 4월 3일

General

import numpy as np
from fractions import Fraction
from tabulate import tabulate

# Convert all elements to Fraction
def to_fraction(array):
    return [Fraction(x).limit_denominator() if isinstance(x, (int, float)) else x for x in array]

# 초기 설정
obj = [5, -5, -13, 0, 0, -10, 0]
A = [
    [-1, 1, 3, 1, 0, 3, 20],
    [12, 4, 10, 0, 1, 5, 90],
]
basic = np.array([4, 5]) - 1  # x5, x6
non_basic = np.array([1, 2, 3]) - 1  # x1, x2, x3, x4


# 초기 배열을 분수로 변환
obj = to_fraction(obj)
A = [to_fraction(row) for row in A]

def print_table():
    headers = ["", "Z"] + [f"x{i+1}" for i in range(len(obj)-1)] + ["RHS"]
    table = [["Z", 1] + [str(x) for x in obj]]
    for i in range(len(A)):
        row = [f"x{basic[i]+1}", 0] + [str(x) for x in A[i]]
        table.append(row)
    print("\nCurrent Simplex Tableau:")
    print(tabulate(table, headers=headers, tablefmt="grid"))

def simplex():
    global obj, A, basic, non_basic
    
    iteration = 1
    while True:
        print(f"\nIteration {iteration}")
        print("=" * 60)
        print_table()

        # 음의 계수 찾기 (entering variable)
        min_rc_idx = None
        min_rc = Fraction(0)
        for j in range(len(obj) - 1):  # RHS 제외
            if obj[j] < min_rc:
                min_rc = obj[j]
                min_rc_idx = j
        
        # 종료 조건: 음수 계수가 없으면 최적
        if min_rc_idx is None or min_rc >= 0:
            print("Optimal solution reached.")
            solution = {f"x{i+1}": Fraction(0) for i in range(len(obj)-1)}
            for i, var_idx in enumerate(basic):
                solution[f"x{var_idx+1}"] = A[i][-1]
            print("Optimal Solution:")
            for var, val in solution.items():
                print(f"{var} = {val}")
            print(f"Objective Value = {obj[-1]}")
            break

        # Pivot 열 선택 및 ratio 계산
        ratios = []
        for i in range(len(A)):
            if A[i][min_rc_idx] > 0:
                ratios.append((A[i][-1] / A[i][min_rc_idx], i))
            else:
                ratios.append((float('inf'), i))
        
        min_ratio, pivot_row = min(ratios)
        if min_ratio == float('inf'):
            print("Unbounded solution detected.")
            break

        print(f"Entering variable: x{min_rc_idx + 1}")
        print(f"Leaving variable: x{basic[pivot_row] + 1}")

        # Pivot 연산
        pivot = A[pivot_row][min_rc_idx]
        A[pivot_row] = [x / pivot for x in A[pivot_row]]
        
        # 다른 행 업데이트
        for i in range(len(A)):
            if i != pivot_row:
                factor = A[i][min_rc_idx]
                A[i] = [A[i][j] - factor * A[pivot_row][j] for j in range(len(obj))]
        
        # 목적함수 업데이트
        factor = obj[min_rc_idx]
        obj = [obj[j] - factor * A[pivot_row][j] for j in range(len(obj))]
        
        # 기본 변수 업데이트
        leaving_var = basic[pivot_row]
        basic[pivot_row] = min_rc_idx
        non_basic[non_basic == min_rc_idx] = leaving_var
        
        iteration += 1

simplex()

Iteration 1
============================================================

Current Simplex Tableau:
+----+-----+------+------+------+------+------+------+-------+
|    |   Z |   x1 |   x2 |   x3 |   x4 |   x5 |   x6 |   RHS |
+====+=====+======+======+======+======+======+======+=======+
| Z  |   1 |    5 |   -5 |  -13 |    0 |    0 |  -10 |     0 |
+----+-----+------+------+------+------+------+------+-------+
| x4 |   0 |   -1 |    1 |    3 |    1 |    0 |    3 |    20 |
+----+-----+------+------+------+------+------+------+-------+
| x5 |   0 |   12 |    4 |   10 |    0 |    1 |    5 |    90 |
+----+-----+------+------+------+------+------+------+-------+
Entering variable: x3
Leaving variable: x4

Iteration 2
============================================================

Current Simplex Tableau:
+----+-----+------+------+------+-------+------+------+-------+
|    |   Z | x1   | x2   |   x3 | x4    |   x5 |   x6 | RHS   |
+====+=====+======+======+======+=======+======+======+=======+
| Z  |   1 | 2/3  | -2/3 |    0 | 13/3  |    0 |    3 | 260/3 |
+----+-----+------+------+------+-------+------+------+-------+
| x3 |   0 | -1/3 | 1/3  |    1 | 1/3   |    0 |    1 | 20/3  |
+----+-----+------+------+------+-------+------+------+-------+
| x5 |   0 | 46/3 | 2/3  |    0 | -10/3 |    1 |   -5 | 70/3  |
+----+-----+------+------+------+-------+------+------+-------+
Entering variable: x2
Leaving variable: x3

Iteration 3
============================================================

Current Simplex Tableau:
+----+-----+------+------+------+------+------+------+-------+
|    |   Z |   x1 |   x2 |   x3 |   x4 |   x5 |   x6 |   RHS |
+====+=====+======+======+======+======+======+======+=======+
| Z  |   1 |    0 |    0 |    2 |    5 |    0 |    5 |   100 |
+----+-----+------+------+------+------+------+------+-------+
| x2 |   0 |   -1 |    1 |    3 |    1 |    0 |    3 |    20 |
+----+-----+------+------+------+------+------+------+-------+
| x5 |   0 |   16 |    0 |   -2 |   -4 |    1 |   -7 |    10 |
+----+-----+------+------+------+------+------+------+-------+
Optimal solution reached.
Optimal Solution:
x1 = 0
x2 = 20
x3 = 0
x4 = 0
x5 = 10
x6 = 0
Objective Value = 100

Big M Method

import numpy as np
from fractions import Fraction
from tabulate import tabulate
from sympy import symbols, simplify, oo

M = symbols('M')

# 초기 설정
obj = [20, 10, 0, M, 0, M, 0]
A = [
    [5, 1, -1, 1, 0, 0, 6],
    [2, 2, 0, 0, -1, 1, 8],
]
basic = np.array([4, 6]) - 1  # x5, x6
non_basic = np.array([1, 2, 3, 5]) - 1  # x1, x2, x3, x4

def to_fraction(array):
    return [Fraction(x).limit_denominator() if isinstance(x, (int, float)) else x for x in array]

# 초기 배열을 분수로 변환
obj = to_fraction(obj)
A = [to_fraction(row) for row in A]

def print_table():
    headers = ["", "Z"] + [f"x{i+1}" for i in range(len(obj)-1)] + ["RHS"]
    table = [["Z", 1] + [str(x) for x in obj]]
    for i in range(len(A)):
        row = [f"x{basic[i]+1}", 0] + [str(x) for x in A[i]]
        table.append(row)
    print("\nCurrent Simplex Tableau:")
    print(tabulate(table, headers=headers, tablefmt="grid"))

def adjust_obj_for_big_m():
    global obj
    print("\nAdjusting Objective Function for Big M Method")
    for i in range(len(basic)):
        basic_var_idx = basic[i]
        if obj[basic_var_idx] != 0:  # 인공변수일 경우(M이 포함된 경우)
            factor = obj[basic_var_idx]
            obj = [simplify(obj[j] - factor * A[i][j]) for j in range(len(obj))]

def simplex():
    global obj, A, basic, non_basic
    
    adjust_obj_for_big_m()
    print_table()
    
    iteration = 1
    while True:
        print(f"\nIteration {iteration}")
        print("=" * 60)
        print_table()

        # 음의 계수 찾기 (entering variable)
        eval_obj = [x.evalf(subs={M: 1e6}) if x.has(M) else float(x) for x in obj[:-1]]
        min_rc_idx = min(range(len(obj)-1), key=lambda j: eval_obj[j])
        
        if eval_obj[min_rc_idx] >= 0:
            print("Optimal solution reached.")
            # 최적 해 출력
            solution = {f"x{i+1}": 0 for i in range(len(obj)-1)}
            for i, var_idx in enumerate(basic):
                solution[f"x{var_idx+1}"] = A[i][-1]
            print("Optimal Solution:")
            for var, val in solution.items():
                print(f"{var} = {val}")
            print(f"Objective Value = {obj[-1]}")
            break

        # Pivot 열 선택 및 ratio 계산
        ratios = []
        for i in range(len(A)):
            if A[i][min_rc_idx] > 0:
                ratios.append((A[i][-1] / A[i][min_rc_idx], i))
            else:
                ratios.append((oo, i))
        
        min_ratio, pivot_row = min(ratios)
        if min_ratio == oo:
            print("Unbounded solution detected.")
            break

        print(f"Entering variable: x{min_rc_idx + 1}")
        print(f"Leaving variable: x{basic[pivot_row] + 1}")

        # Pivot 연산
        pivot = A[pivot_row][min_rc_idx]
        A[pivot_row] = [simplify(x / pivot) for x in A[pivot_row]]
        
        # 다른 행 업데이트
        for i in range(len(A)):
            if i != pivot_row:
                factor = A[i][min_rc_idx]
                A[i] = [simplify(A[i][j] - factor * A[pivot_row][j]) for j in range(len(obj))]
        
        # 목적함수 업데이트
        factor = obj[min_rc_idx]
        obj = [simplify(obj[j] - factor * A[pivot_row][j]) for j in range(len(obj))]
        
        # 기본 변수 업데이트
        leaving_var = basic[pivot_row]
        basic[pivot_row] = min_rc_idx
        non_basic[non_basic == min_rc_idx] = leaving_var
        
        iteration += 1

simplex()

Adjusting Objective Function for Big M Method

Current Simplex Tableau:
+----+-----+----------+----------+------+------+------+------+-------+
|    |   Z | x1       | x2       | x3   |   x4 | x5   |   x6 | RHS   |
+====+=====+==========+==========+======+======+======+======+=======+
| Z  |   1 | 20 - 7*M | 10 - 3*M | M    |    0 | M    |    0 | -14*M |
+----+-----+----------+----------+------+------+------+------+-------+
| x4 |   0 | 5        | 1        | -1   |    1 | 0    |    0 | 6     |
+----+-----+----------+----------+------+------+------+------+-------+
| x6 |   0 | 2        | 2        | 0    |    0 | -1   |    1 | 8     |
+----+-----+----------+----------+------+------+------+------+-------+

Iteration 1
============================================================

Current Simplex Tableau:
+----+-----+----------+----------+------+------+------+------+-------+
|    |   Z | x1       | x2       | x3   |   x4 | x5   |   x6 | RHS   |
+====+=====+==========+==========+======+======+======+======+=======+
| Z  |   1 | 20 - 7*M | 10 - 3*M | M    |    0 | M    |    0 | -14*M |
+----+-----+----------+----------+------+------+------+------+-------+
| x4 |   0 | 5        | 1        | -1   |    1 | 0    |    0 | 6     |
+----+-----+----------+----------+------+------+------+------+-------+
| x6 |   0 | 2        | 2        | 0    |    0 | -1   |    1 | 8     |
+----+-----+----------+----------+------+------+------+------+-------+
Entering variable: x1
Leaving variable: x4

Iteration 2
============================================================

Current Simplex Tableau:
+----+-----+------+-----------+-----------+-----------+------+------+--------------+
|    |   Z |   x1 | x2        | x3        | x4        | x5   |   x6 | RHS          |
+====+=====+======+===========+===========+===========+======+======+==============+
| Z  |   1 |    0 | 6 - 8*M/5 | 4 - 2*M/5 | 7*M/5 - 4 | M    |    0 | -28*M/5 - 24 |
+----+-----+------+-----------+-----------+-----------+------+------+--------------+
| x1 |   0 |    1 | 1/5       | -1/5      | 1/5       | 0    |    0 | 6/5          |
+----+-----+------+-----------+-----------+-----------+------+------+--------------+
| x6 |   0 |    0 | 8/5       | 2/5       | -2/5      | -1   |    1 | 28/5         |
+----+-----+------+-----------+-----------+-----------+------+------+--------------+
Entering variable: x2
Leaving variable: x6

Iteration 3
============================================================

Current Simplex Tableau:
+----+-----+------+------+------+---------+------+----------+-------+
|    |   Z |   x1 |   x2 | x3   | x4      | x5   | x6       | RHS   |
+====+=====+======+======+======+=========+======+==========+=======+
| Z  |   1 |    0 |    0 | 5/2  | M - 5/2 | 15/4 | M - 15/4 | -45   |
+----+-----+------+------+------+---------+------+----------+-------+
| x1 |   0 |    1 |    0 | -1/4 | 1/4     | 1/8  | -1/8     | 1/2   |
+----+-----+------+------+------+---------+------+----------+-------+
| x2 |   0 |    0 |    1 | 1/4  | -1/4    | -5/8 | 5/8      | 7/2   |
+----+-----+------+------+------+---------+------+----------+-------+
Optimal solution reached.
Optimal Solution:
x1 = 1/2
x2 = 7/2
x3 = 0
x4 = 0
x5 = 0
x6 = 0
Objective Value = -45

Grubi

from gurobipy import *

model = Model("ex4.4-6")
model.setParam(GRB.Param.OutputFlag, 0)

xab = model.addVar(vtype=GRB.CONTINUOUS, name="xab")
xac = model.addVar(vtype=GRB.CONTINUOUS, name="xac")
xbd = model.addVar(vtype=GRB.CONTINUOUS, name="xbd")
xbe = model.addVar(vtype=GRB.CONTINUOUS, name="xbe")
xcd = model.addVar(vtype=GRB.CONTINUOUS, name="xcd")
xce = model.addVar(vtype=GRB.CONTINUOUS, name="xce")
xde = model.addVar(vtype=GRB.CONTINUOUS, name="xde")
xdf = model.addVar(vtype=GRB.CONTINUOUS, name="xdf")
xef = model.addVar(vtype=GRB.CONTINUOUS, name="xef")
xfa = model.addVar(vtype=GRB.CONTINUOUS, name="xfa")

model.setObjective(xfa, GRB.MAXIMIZE)

model.addConstr(xab + xac - xfa == 0)
model.addConstr(xbd + xbe - xab == 0)
model.addConstr(xcd + xce - xac == 0)
model.addConstr(xde + xdf - xbd - xcd == 0)
model.addConstr(xef - xbe - xce - xde == 0)
model.addConstr(xfa - xdf - xef == 0)

model.addConstr(xab <= 9)
model.addConstr(xac <= 7)
model.addConstr(xbd <= 7)
model.addConstr(xbe <= 2)
model.addConstr(xcd <= 4)
model.addConstr(xce <= 6)
model.addConstr(xde <= 3)
model.addConstr(xdf <= 6)
model.addConstr(xef <= 9)

model.optimize()

for var in model.getVars():
    print(f"{var.varName}: {var.x}")
print("Obj: ", model.objVal)
Restricted license - for non-production use only - expires 2026-11-23
xab: 8.0
xac: 7.0
xbd: 6.0
xbe: 2.0
xcd: 1.0
xce: 6.0
xde: 1.0
xdf: 6.0
xef: 9.0
xfa: 15.0
Obj:  15.0
맨 위로