Lineer Programlama: Optimizasyon Sanatı

Kısıtlı kaynaklarla maksimum verim: Matematiksel optimizasyonun gücü

maximize c^T x
subject to: Ax ≤ b
# Optimal çözüm bul

Lineer Programlama Nedir?

Lineer Programlama (LP), lineer kısıtlar altında lineer bir amaç fonksiyonunu optimize eden (maksimize veya minimize eden) matematiksel optimizasyon tekniğidir. 1940'larda George Dantzig tarafından geliştirilen Simplex yöntemi ile pratik çözüm bulmuştur. LP, işletme, ekonomi, mühendislik ve birçok alanda karar verme süreçlerinde kullanılır.

Tarihçe
LP'nin kökleri II. Dünya Savaşı sırasında askeri lojistik problemlerinin çözümüne dayanır. George Dantzig 1947'de Simplex algoritmasını geliştirdi. Bu algoritma, günümüzde de LP problemlerinin çözümünde temel yöntemdir.
Genel LP Formu:

Maksimize/Minimize: z = c₁x₁ + c₂x₂ + ... + cₙxₙ
Kısıt altında:
a₁₁x₁ + a₁₂x₂ + ... + a₁ₙxₙ ≤/=/≥ b₁
a₂₁x₁ + a₂₂x₂ + ... + a₂ₙxₙ ≤/=/≥ b₂
⋮
aₘ₁x₁ + aₘ₂x₂ + ... + aₘₙxₙ ≤/=/≥ bₘ
x₁, x₂, ..., xₙ ≥ 0
LP'nin Temel Bileşenleri:
  • Karar Değişkenleri: Optimize edilecek değerler (x₁, x₂, ...)
  • Amaç Fonksiyonu: Maksimize veya minimize edilecek lineer fonksiyon
  • Kısıtlar: Karar değişkenlerini sınırlayan lineer eşitsizlik/eşitlikler
  • Non-negativity: Değişkenlerin negatif olmaması koşulu
Popüler LP Çözücüler: CPLEX, Gurobi, GLPK, CBC, HiGHS, Python (PuLP, SciPy, CVXPY).

LP'nin gücü, gerçek hayat problemlerinin matematiksel modellere dönüştürülmesinde yatar. Üretim planlaması, kaynak tahsisi, portföy optimizasyonu, ulaştırma ağları gibi birçok alanda kullanılır.

Temel Kavramlar

LP problemlerini anlamak için temel kavramları bilmek kritiktir. Bu kavramlar, problemin modellenmesinden çözümün yorumlanmasına kadar her aşamada kullanılır.

Model Kurma Adımları

1. Problemi Tanımla → Neyi optimize etmek istiyoruz?
2. Karar Değişkenlerini Belirle → Hangi kararları veriyoruz?
3. Amaç Fonksiyonunu Formüle Et → Maksimize/minimize edilecek
4. Kısıtları Tanımla → Sınırlamalar neler?
5. Non-negativity Koşulu → Değişkenler ≥ 0

# Örnek: Mobilya Üretimi Problemi
# Bir fabrika masa ve sandalye üretir
# Masa: 4 saat işçilik, 3 kg ahşap, 50 TL kar
# Sandalye: 2 saat işçilik, 2 kg ahşap, 30 TL kar
# Kısıtlar: 40 saat işçilik, 30 kg ahşap

import pulp as lp

# Problem tanımla
prob = lp.LpProblem("Mobilya_Uretimi", lp.LpMaximize)

# Karar değişkenleri
x1 = lp.LpVariable("Masa", lowBound=0, cat='Continuous')
x2 = lp.LpVariable("Sandalye", lowBound=0, cat='Continuous')

# Amaç fonksiyonu
prob += 50 * x1 + 30 * x2, "Toplam_Kar"

# Kısıtlar
prob += 4 * x1 + 2 * x2 <= 40, "Iscilik_Kisiti"
prob += 3 * x1 + 2 * x2 <= 30, "Ahsap_Kisiti"

# Çözüm
prob.solve()
print(f"Status: {lp.LpStatus[prob.status]}")
print(f"Optimal çözüm: Masa = {x1.varValue}, Sandalye = {x2.varValue}")
print(f"Maksimum kar: {lp.value(prob.objective)} TL")
Status: Optimal
Optimal çözüm: Masa = 6.0, Sandalye = 8.0
Maksimum kar: 540.0 TL
Uygulanabilir Bölge: Tüm kısıtları sağlayan noktaların oluşturduğu küme. LP problemlerinde bu bölge her zaman konvekstir (eğer boş değilse). Optimal çözüm her zaman bu bölgenin bir köşe noktasında bulunur.

Grafik Çözüm Yöntemi

İki değişkenli LP problemleri grafik yöntemi ile çözülebilir. Bu yöntem görsel bir anlayış sağlar ve LP'nin temel prensiplerini öğrenmek için idealdir.

Grafik Çözüm Adımları
1. Kısıtları grafikte çiz (doğrular)
2. Uygulanabilir bölgeyi belirle (kesişim)
3. Köşe noktalarını bul
4. Her köşede amaç fonksiyonunu hesapla
5. En iyi değeri veren noktayı seç
import numpy as np
import matplotlib.pyplot as plt

def grafik_cozum():
    x1 = np.linspace(0, 15, 100)
    
    # Kısıt çizgileri
    x2_kisit1 = (40 - 4 * x1) / 2  # İşçilik kısıtı
    x2_kisit2 = (30 - 3 * x1) / 2  # Ahşap kısıtı
    
    plt.figure(figsize=(10, 8))
    
    # Kısıtları çiz
    plt.plot(x1, x2_kisit1, 'r-', label='İşçilik: 4x₁ + 2x₂ ≤ 40')
    plt.plot(x1, x2_kisit2, 'b-', label='Ahşap: 3x₁ + 2x₂ ≤ 30')
    
    # Uygulanabilir bölgeyi gölgele
    x2_uygulanabilir = np.minimum(x2_kisit1, x2_kisit2)
    x2_uygulanabilir = np.maximum(x2_uygulanabilir, 0)
    plt.fill_between(x1, 0, x2_uygulanabilir, alpha=0.3, 
                     color='green', label='Uygulanabilir Bölge')
    
    # Köşe noktaları
    corner_points = [(0, 0), (0, 15), (6, 8), (10, 0)]
    
    for point in corner_points:
        plt.plot(point[0], point[1], 'ko', markersize=8)
        plt.annotate(f'({point[0]}, {point[1]})', point)
    
    # Amaç fonksiyonu çizgileri
    for z in [200, 400, 540]:
        x2_obj = (z - 50 * x1) / 30
        plt.plot(x1, x2_obj, '--', alpha=0.7, label=f'z = {z}')
    
    plt.xlim(0, 12)
    plt.ylim(0, 20)
    plt.xlabel('x₁ (Masa)')
    plt.ylabel('x₂ (Sandalye)')
    plt.title('LP Problemi - Grafik Çözüm')
    plt.legend()
    plt.grid(True, alpha=0.3)
    plt.show()
Sınırlama: Grafik yöntem sadece 2-3 değişkenli problemlerde kullanılabilir. Gerçek hayat problemlerinde genellikle yüzlerce hatta binlerce değişken olduğu için Simplex yöntemi kullanılır.

Simplex Yöntemi

Simplex yöntemi, LP problemlerini çözmek için en yaygın kullanılan algoritmadır. George Dantzig tarafından 1947'de geliştirilmiş ve günümüzde hala temel yöntemdir. Algoritma, uygulanabilir bölgenin köşe noktaları arasında sistematik olarak gezinerek optimal çözümü bulur.

Simplex Algoritması
1. Standardizasyon: Problemi standart forma dönüştür
2. İlk Çözüm: Başlangıç temel uygulanabilir çözümü bul
3. Optimallik Testi: Mevcut çözüm optimal mi?
4. Pivotlama: Daha iyi bir köşe noktasına geç
5. Tekrar: Optimal çözüm bulunana kadar devam et
Standart LP Formu:

Minimize: c^T x
Kısıt altında: Ax = b
x ≥ 0

(Slack/surplus/artificial değişkenler eklenerek)
from scipy.optimize import linprog
import numpy as np

def simplex_cozum():
    # Amaç fonksiyonu (minimize için negatif)
    c = [-50, -30]
    
    # Eşitsizlik kısıtları
    A_ub = [[4, 2], [3, 2]]
    b_ub = [40, 30]
    
    # Değişken sınırları
    bounds = [(0, None), (0, None)]
    
    # Simplex ile çözüm
    result = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
    
    if result.success:
        print(f"Masa: {result.x[0]:.2f}, Sandalye: {result.x[1]:.2f}")
        print(f"Maksimum kar: {-result.fun:.2f} TL")
        print(f"İterasyonlar: {result.nit}")

simplex_cozum()
Masa: 6.00, Sandalye: 8.00
Maksimum kar: 540.00 TL
İterasyonlar: 2

Simplex Tablosu

Simplex tablosu, algoritmanın her adımını görsel olarak takip etmemizi sağlar:

# Simplex tablosu örneği (manuel hesaplama)
def simplex_tableau():
    print("Başlangıç Simplex Tablosu:")
    print("="*60)
    print("    | x1  x2  s1  s2 |  RHS")
    print(" s1 |  4   2   1   0 |  40")
    print(" s2 |  3   2   0   1 |  30")
    print(" z  |-50 -30   0   0 |   0")
    print("="*60)
    
    print("\nOptimal Simplex Tablosu:")
    print("="*60)
    print("    | x1  x2  s1  s2 |  RHS")
    print(" x2 |  0   1  0.5 -1.5|   8")
    print(" x1 |  1   0 -0.5  1.5|   6")
    print(" z  |  0   0  25  45 | 540")
    print("="*60)

simplex_tableau()
Simplex Tablosu Yorumu:
  • Son tabloda tüm c değerleri ≥ 0 ise optimal çözüm bulunmuştur
  • Shadow price'lar (gölge fiyatları) slack değişkenlerinin z satırındaki katsayılarıdır
  • 25 ve 45 değerleri, kısıtların bir birim artırılması durumunda amaç fonksiyonundaki artışı gösterir

Duality Teorisi

Her LP probleminin bir dual (ikil) problemi vardır. Primal problem kaynakların nasıl kullanılacağını optimize ederken, dual problem bu kaynakların değerini belirler. Duality teorisi, LP'nin en güçlü ve zarif yönlerinden biridir.

Primal-Dual İlişkisi
Primal Problem (Maksimizasyon): Kaynakları nasıl kullanacağız?
Dual Problem (Minimizasyon): Kaynakların değeri ne olmalı?
Güçlü Duality: Her iki problemin optimal değeri eşittir!
Primal ve Dual Problem:

Primal: max c^T x, Ax ≤ b, x ≥ 0
Dual: min b^T y, A^T y ≥ c, y ≥ 0

Optimal değerler: c^T x* = b^T y*
# Mobilya probleminin dual çözümü
def dual_problem():
    # Dual problem: minimize 40y1 + 30y2
    # subject to: 4y1 + 3y2 >= 50 (masa kısıtı)
    #             2y1 + 2y2 >= 30 (sandalye kısıtı)
    
    from scipy.optimize import linprog
    
    # Dual amaç fonksiyonu (minimize)
    c_dual = [40, 30]
    
    # Dual kısıtları (>= formunu <= çevirmek için -1 ile çarp)
    A_dual = [[-4, -3], [-2, -2]]
    b_dual = [-50, -30]
    
    bounds_dual = [(0, None), (0, None)]
    
    result_dual = linprog(c_dual, A_ub=A_dual, b_ub=b_dual, 
                         bounds=bounds_dual, method='highs')
    
    print("Dual Problem Çözümü:")
    print(f"y1 (İşçilik shadow price): {result_dual.x[0]:.2f} TL/saat")
    print(f"y2 (Ahşap shadow price): {result_dual.x[1]:.2f} TL/kg")
    print(f"Dual optimal değer: {result_dual.fun:.2f} TL")

dual_problem()
Dual Problem Çözümü:
y1 (İşçilik shadow price): 25.00 TL/saat
y2 (Ahşap shadow price): 45.00 TL/kg
Dual optimal değer: 540.00 TL
Shadow Price Yorumu: İşçilik kaynağını 1 saat artırırsak kar 25 TL artar. Ahşap kaynağını 1 kg artırırsak kar 45 TL artar. Bu bilgi, kaynak yatırım kararları için kritiktir!

Duyarlılık Analizi

Duyarlılık analizi, LP problemindeki parametrelerin değişmesi durumunda optimal çözümün nasıl etkileneceğini inceler. Bu analiz, gerçek dünyada belirsizlik altında karar verme için hayati önemdedir.

Duyarlılık Analizi Türleri:
  • Amaç Fonksiyonu Katsayıları: Kar/maliyet değişimleri
  • Sağ Taraf Sabitleri (RHS): Kaynak miktarlarındaki değişimler
  • Teknoloji Katsayıları: Üretim süreçlerindeki değişimler
  • Yeni Değişken Ekleme: Yeni ürün/hizmet fırsatları
# Duyarlılık analizi örneği
import numpy as np
import matplotlib.pyplot as plt

def sensitivity_analysis():
    # Ahşap miktarının optimal kara etkisi
    ahsap_miktari = np.range(25, 36)
    optimal_kar = []
    
    for ahsap in ahsap_miktari:
        # Her ahşap miktarı için LP çöz
        c = [-50, -30]
        A_ub = [[4, 2], [3, 2]]
        b_ub = [40, ahsap]
        bounds = [(0, None), (0, None)]
        
        result = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds)
        optimal_kar.append(-result.fun if result.success else 0)
    
    plt.figure(figsize=(10, 6))
    plt.plot(ahsap_miktari, optimal_kar, 'bo-')
    plt.axhline(y=540, color='r', linestyle='--', alpha=0.7, 
                label='Mevcut Optimal Kar')
    plt.axvline(x=30, color='g', linestyle='--', alpha=