Asorti Optimizasyonu: Python PuLP ve IBM Ilog Cplex Uygulaması

Şükrü İmre, PhD
7 min readMay 5, 2021

Bu yazı, bir perakende firmasının asorti optimizasyonu problemine çözüm yaklaşımını anlatır. Python PuLP ve IBM Ilog Cplex platformları kullanılarak iki çözüm önerildi. Bu problem çerçevesinde probleme ait matematiksel model kuruldu ve kodlandı. Python PuLP çözümünde Orkun Berk’in yazmış olduğu “PuLP ile Doğrusal Optimizasyon 1” ve IBM Ilog Cplex çözümünde Sabri Suyunu’nun yazmış olduğu “IBM CPLEX MSSQL Database Connection” yazılarının zengin içeriklerinden yararlanıldı. Bu içeriklerden elde edilen bilgiler doğrultusunda “asorti optimizasyonu” problemini çözüldü.

Problem Tanımı:

X perakende firması 600'ü aşkın mağazasıyla dünyanın çeşitli bölgelerinde faaliyet gösteriyor. Bu firmanın faaliyetleri arasında, sezon sonunda satmayan ürünleri iade depolarına çekmek vardır. Örneğin; yaz sezonuna geçiş yapıldığı dönemde, kış sezonundan sergilenen yeşil renkli kazakların satmayanları mağazalardan toplatılarak depolara iade alınır. Mağazacılık, sezon geçişlerinde bunun gibi iade edilecek ürünleri karışık (kazak, jean, kaban, gömlek vb.) bir şekilde kolilere koyarak iade depolarına gönderir. İade depolarına gelen bu ürünler, bir sonraki sene gönderilmek veya farklı ülkelerdeki mağazalara sevk edilmek üzere asorti yapılır.

Asorti, ürünlerin farklı bedenlerinin farklı miktarlarda bir arada bulunduğu bütündür. Örneğin; yeşil renkli bir kazağın XS, S, M, L ve XL bedenlerinden birer adet paketlenmiş hali yeşil kazağa ait bir asortidir.

Firma, farklı mağazalar tarafından gönderilen çeşitli ürünlerin bulunduğu koliler içerisinde bulunan kazak ve gömleklere ait asorti yapmak istiyor. Firmanın belirlediği kısıtlar şu şekildedir:

  • Bir çeşide ait asorti minimum 4 maksimum 6 beden içermeli,
  • Bir asortiye ilgili çeşide ait bedenden bir adet koyulmalı,
  • Her bir çeşit için farklı bir asorti planlanmalı (bir asorti içinde kazak ve gömlek birlikte bulunmamalı).

İade depolarda bulunan kazak ve gömleklere ait stok bilgileri aşağıda verilmiştir.

Firma,

“stokta en az ürün kalacak şekilde kazak ve gömleklere ait asortilerin yapılmasını”

istemektedir.

Matematiksel Model:

Firmanın belirlediği kısıtlar altında ve isteği doğrultusunda, tamsayılı karışık doğrusal programlama modeli kurulmuştur. Model kapsamında belirlenen karar değişkenleri ve modele ait parametre ve kümeler aşağıda verilmiştir.

Problem tanımında belirtildiği üzere, kazak ve gömlek olmak üzere iki çeşit ürün vardır ve i indisiyle gösterilmiştir. “XS”den “XXL”ye kadar toplamda 6 farklı beden vardır ve j indisiyle ifade edilmiştir. Yapılacak asorti numaraları k indisiyle gösterilmiştir.

Kurulan matematiksel modelde; Ti, i. çeşidin toplam stok adetini, Sij, i. çeşidin j. bedenine ait stok miktarı, M4, asorti içerisinde bulunacak minimum miktar ve M6, asorti içerisinde bulunacak maksimum miktarı temsil eder. M katsayısı, ürünlerin asortiye mümkün olduğunca koyulmasını sağlayan yeterince büyük bir sayıyı temsil eder. Burada kazak ve gömleğe ait toplam stok miktarı sırasıyla 289 ve 225 olup toplam stok miktarı 514'tür. Asorti içerisinde bulunacak miktar (aynı zamanda beden sayısı) minimum 4 ve maksimum 6'dır. M değeri 10 olarak alınmıştır. olarak Sij değerleri yukarıda paylaşılan stok verisinden görülebilir. Örnek vermek gerekirse; S beden kazağın stoğu 26 adet iken M beden gömleğe ait stok miktarı 21 adettir.

Xijk karar değişkeni, açılan asortiye ilgili çeşidin ilgili bedenin koyulup koyulmayacağını ifade eder. Tanımlanan problem özelinde bu karar değişkeni, kazak çeşidi için açılan 1. asortiye S bedeni atanırsa 1 değerini alır. Böylelikle, kazak için açılan bu asortiye 1 adet S bedenini koyulduğu anlaşılır. Diğer yandan, Yik ise i. çeşit için k. asortinin açılıp açılmamasına karar veren değişkendir. Örneğin; kazak için 1 numaralı asorti açılırsa bu değişkenin değeri 1 olur.

Firmanın belirttiği üzere en az stok kalacak şekilde modelleme yapılması gerekiyor. Bu istekle, belirlenen kısıtlar altında minimum stoku garanti eden amaç fonksiyonu aşağıda gösterilmiştir.

Yukarıda bahsedilen firma kısıtlarının matematiksel ifadesi aşağıda verilmiştir.

Minimum miktar kısıdı, oluşturulacak bir asortinin içerisine minimum 4 adet ve 4 farklı bedenin girmesini sağlar. Maksimum miktar kısıdı, aynı şekilde bir asorti içerisine maksimum 6 adet ve 6 farklı beden girmesini garantiler. Stok kısıdı, her çeşit ve o çeşidin bedenine ait toplam stok miktarının aşılmamasını ifade eder. Örneğin bu kısıt, L beden kazağın 20 adetten fazla asortilere koyulmamasını sağlar. Asorti içi tek çeşit olma kısıdı, açılan asortinin kazak ve gömleklerden sadece birini içermesini garantiler. Toplam stoğu aşmama kısıdı, kazak ve gömlekler için yapılan asortilerde kullanılan bedenlerin toplam çeşit stoğunu aşmamasını ifade eder. Son olarak, Xijk ve Yik birer binary değişkendir ve ya 0 ya da 1 değerini alabilirler.

Python PuLP ile Çözüm:

Bu çalışma, yukarıda detaylarıyla anlatılan matematiksel modelin çözümü elde etmek için ilk olarak Python’un PuLP kütüphanesinden yararlanıyor.

İlk olarak PuLP kütüphanesi yüklenir ve yukarıda tarif edilen küme ve parametreler tanımlanır.

# PuLP kütüphanesi yüklenir. 
from pulp import *
#kümeler
cesit=['Kazak','Gömlek'] # i
beden=['XS','S','M','L','XL','XXL'] # j
asorti_id=list(range(1,150)) # k
#parametreler# Sij değerleri dictionary olarak tanımlanır.
stok_dict={}
stok_dict[('Kazak','XS')]=89
stok_dict[('Kazak','S')]=26
stok_dict[('Kazak','M')]=48
stok_dict[('Kazak','L')]=20
stok_dict[('Kazak','XL')]=32
stok_dict[('Kazak','XXL')]=74
stok_dict[('Gömlek','XS')]=23
stok_dict[('Gömlek','S')]=54
stok_dict[('Gömlek','M')]=21
stok_dict[('Gömlek','L')]=87
stok_dict[('Gömlek','XL')]=35
stok_dict[('Gömlek','XXL')]=5
# Ti değerleri dictionary olarak tanımlanır.
toplam_stok={}
toplam_stok['Kazak']=289
toplam_stok['Gömlek']=225
# M4 ve M6 değerleri belirlenir.
M4=4
M6=6
M=10

Daha sonra karar değişkenleri ve amaç fonksiyonu tanımlanır. Karar değişkenleri binary olarak girilir. Minimum stok miktarı amaçlandığından modelin türü minimizasyon olarak düzenlenir ve amaç fonksiyonu tanımlanır.

# karar değişkenleri tanımlanır.
x = LpVariable.dicts("", [(i,j,k) for i in cesit for j in beden for k in asorti_id], cat='Binary')
y = LpVariable.dicts("", [(i,k) for i in cesit for k in asorti_id], cat='Binary')
# problemin türü belirlenir.
model=LpProblem("Min_AçıkAdet", LpMinimize)
# amaç fonksiyonu tanımlanır.
model += lpSum([y[(i,k)] for i in cesit for k in asorti_id])-M*lpSum([x[(i,j,k)] for i in cesit for j in beden for k in asorti_id])

Son olarak modele ait kısıtlar — minimum miktar, maksimum miktar, stok ve toplam stoku aşmama kısıtları — eklenir.

# modele minimum ve maksimum miktar kısıtları eklenir.
for i in cesit:
for k in asorti_id:
model+=lpSum([x[(i,j,k)] for j in beden])>=M4*y[(i,k)]
model+=lpSum([x[(i,j,k)] for j in beden])<=M6*y[(i,k)]
# modele bedenlere ait stok kısıdı eklenir.
for i in cesit:
for j in beden:
model += lpSum([x[(i,j,k)] for k in asorti_id])<=stok_dict[(i,j)]
# modele asorti içerisinde tek çeşit ürün kısıdı eklenir.
for k in asorti_id:
for j in beden:
model += lpSum([x[(i,j,k)] for i in cesit])<=1

# modele çeşit bazında toplam stok kısıdı eklenir.
for i in cesit:
model+=lpSum([x[(i,j,k)] for j in beden for k in asorti_id])<=toplam_stok[i]

Model çözdürülür.

# model çözümü
model.solve()
print("Çözülen Model : {}".format(LpStatus[model.status]))
Çözülen Model : Optimal

Değişkenlere ait değerleri, aşağıdaki kod yardımıyla excele aktarılır.

# değişkene ait sonuçlar ortaya çıkarılır. 
import pandas as pd
import openpyxl
var_name= []
var_value=[]
for v in model.variables():
if v.varValue>0:
var_name.append(v.name)
var_value.append(v.varValue)
# sonuçlar dataframe olarak oluşturulur.
df_result=pd.DataFrame(zip(var_name,var_value), columns=['Değişken','Değer'])
df_result.head()
# sonuçlar excele aktarılır.
df_result.to_excel("output.xlsx")

Daha sonra sonuçlar incelenir. Aşağıda örnek olması açısından Xijk değişkeninin 1 olduğu atamalar paylaşılmıştır.

Bu sonuç, Gömlek için açılan 10., 107., 110., 112. ve 113. asortiye L bedeninin koyulduğunu söylemektedir.

IBM Ilog Cplex ile Çözüm:

Python PuLP çözümü için kodlanan modelin aynısı IBM Ilog Cplex’te de kodlanarak çözüm elde edilmiştir. Ilog iki farklı kod bloğu ile çalışır. İlk blok, matematiksel modelin kodlandığı “mod” kod bloğudur. Diğeri ise model kod bloğundan “…” olarak belirtilen tuplelara ait verilerin okunduğu “dat” kod bloğudur.

İlk olarak “dat” kod bloğunu paylaşılmıştır. Probleme ait veriler Asorti_Opt isimli excel dosyasından aşağıda belirtilen bağlantı yardımıyla okunur.

SheetConnection sheet("Asorti_Opt.xlsx");OptionBedenStok from SheetRead(sheet,"stok!A2:C13");
Asorti_Id from SheetRead(sheet,"asorti!A2:B201");
ToplamStok from SheetRead(sheet,"toplam_stok!A2:B201");

Aşağıda parametre ve tupleları içeren “mod” kod bloğuna gösterilmiştir.

// parameters
int M6=6;
int M4=4;
int M=10;
//tuples
// çeşit beden stok bilgisinin tutulduğu tuple oluşturulur.
tuple OBS {
string option;
string beden;
int stok_miktar;
} {OBS} OptionBedenStok=...;
// çeşit bazındaki toplam stok bilgilerine ait tuple oluşturulur.
tuple TS {
string option;
int stok_miktar;
} {TS} ToplamStok=...;
// çeşit bazında potansiyel asorti numaralarına ait tuple oluşturulur.
tuple Asorti_ID
{
string option;
int asorti_id;
} {Asorti_ID} Asorti_Id=...;
// X değişkenine ait tuple oluşturulur.
tuple Degisken
{
string option;
string beden;
int asorti_id;
} {Degisken} DegiskenTuple= {<k.option, k.beden, l.asorti_id>| k in OptionBedenStok, l in Asorti_Id : k.option==l.option};

Karar değişkenleri tanımlanarak amaç fonksiyonu kodlanır.

// karar değişkenleri tanımlanır.
dvar boolean x[DegiskenTuple];
dvar boolean y[Asorti_Id];
// amaç fonksiyonu kurulur.
minimize sum(k in Asorti_Id) y[k]-M*sum(i in DegiskenTuple)x[i];

Kısıtlar tanımlanır.

subject to {
maximum_ve_minimum_miktar_kisidi: forall (k in Asorti_Id)
{
sum (i in DegiskenTuple : i.option==k.option && i.asorti_id==k.asorti_id) x[i]>=M4*y[k];
sum (i in DegiskenTuple : i.option==k.option && i.asorti_id==k.asorti_id) x[i]<=M6*y[k];
}
stok_kisidi: forall (s in OptionBedenStok)
{
sum(i in DegiskenTuple : i.option==s.option && i.beden==s.beden) x[i]<=s.stok_miktar;
}
toplam_stok_kisidi: forall (t in ToplamStok)
{
sum(i in DegiskenTuple : i.option==t.option) x[i]<=t.stok_miktar;
}
asorti_ici_cesit_kisidi: forall (l in DegiskenTuple)
{
sum(i in DegiskenTuple : i.beden==l.beden && i.asorti_id==l.asorti_id) x[i]<=1;
}
}

Sonuç olarak, her iki ortamda kodlanıp çözdürülen model, toplamda 105 asorti açılacağını optimum değer olarak bulmuştur. Gömlek için oluşturulan asorti sayısı 42 iken kazağa ait sayı ise 63 olarak ortaya çıkmıştır. Stokta kalan toplam ürün miktarı da 94'tür.

Pek çok sektöre ait optimizasyon modellerin Python PuLP ve IBM Ilog Cplex ortamlarında rahatlıkla çözülebilir. Bu yazıda örnek bir problem ile her iki ortamda nasıl kodlama yapılabileceği gösterilmiştir.

Çözüm aşamasında, optimizasyon alanında öncü ve tecrübeli Orkun Berk ve Sabri Suyunu profesyonellerinin zengin içeriklerinden yararlandım. Bu yazının çatısını oluşturması anlamında ilham oldukları için kendilerine teşekkür ederim.

Şükrü İMRE

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Şükrü İmre, PhD
Şükrü İmre, PhD

Written by Şükrü İmre, PhD

// Head of Data Science at Yapı Kredi Bank // Guest Lecturer at MEF University // Author at Harvard Business Review Türkiye // Author at Medium

No responses yet

Write a response