Python tabanlı enerji depolama simulasyonu ve Türkiye elektrik sistemine etkisinin incelemesi¶Barış Sanlı (barissanli2@gmail.com) Giriş¶Bu çalışmanın ana sorusu: Enerji depolama sistemlerinin Türkiye elektrik sistemini nasıl etkileyebileceğidir. Python üzerinde çalıştırılan bir kod ile 2018 yılı Türkiye elektrik talep verileri kullanılarak enerji depolamanın muhtemel etkisi incelenmiştir. Model çok kısa olduğundan sadece fikir vermek amaçlanmıştır. Yoksa gerçek veya karar almak için kullanılamaz. Yapılan çalışma tamamen hobi amaçlıdır. Çalışmada kullanılan
adresinden indirilebilir. Excel dosyası¶Linkte verilen excel dosyasında 2018 yılı elektrik sisteminde üretilen lisanslı ve lisanssız tüm üretim ve fiyat rakamları vardır. Verilerin kaynağı EPİAŞ'tır. Excel dosyasında iki tane alt sayfa var. Biri "2018-saatlik" ve diğeri de "2018-gunluk". Bu yazıda saatlik verileri kullanacağız. Program kodları¶Python'da yazılan kodları çalıştırabilen Jupyter defter dosyası github dan erişilebilir. UYARI: Depolama algoritması her parametrede stabil değil, bunu en altta teknik kısımda anlatıyorum Başlangıç¶Önce programımızda kullanacağımız kütüphaneleri yüklüyoruz. Ben Jupyter'de doğrudan aşağıdaki iki satırı kullanarak, kütüphaneleri daha az kod ile yüklemeyi tercih ediyorum In [1]:
%pylab inline
import pandas as pd
Şimdi de Excel dosyasındaki 2018 yılı saatlik verilerini prices değişkenine yükleyelim. Excel dosyasındaki değişkenler Türkçe. Kodda İngilizce kısımlar ise uluslararası paylaşım sağlamak içindir. In [2]:
prices=pd.read_excel("2018-elektrik.xlsx",sheet_name="2018-saatlik")
Depolama Benzeticisi(Simülatörü)¶Depolama benzeticisi toplam bir elektrik depolama hacmi (Örneğin 1000 MWh) ve bu depolama hacminin(storage_amount) şarj ve deşarj edilme sürelerini belirleyen storage_time değişkeninden oluşur. Yani şarj süresi 4 saat ise, önümüzdeki 24 saatte 4 saat şarj, 4 saat deşarj şeklinde toplam 4000 MWh'lik bir pil kapasitesi kullanıyor demektir. Kod 24 saatlik dilimlere bakıyor. Bu 24 saatlik dilimlerde en düşük talep saatlerinde kendini şarj ederek talebi yükseltiyor, en yüksek talep dönemlerinde ise şarj ettiği elektriği geri veriyor. Aslında çok daha basit bir kod ile başladım. İlk depolama simulatörü¶İlk depolama simülatörü görüldüğü üzere çok basit bir koddan oluşuyordu. Satır satır
In [3]:
def storage_player(df,storage_amount=1000, storage_time=4):
sorted24=pd.DataFrame(df.values).sort_values(by=0,ascending=True)
storage_up2=(sorted24[0:storage_time].mean().values+(storage_amount/storage_time))[0]
sorted24["storage"]=0
# first charge
for i in range(0,storage_time):
sorted24.iloc[i,1]=storage_up2-sorted24.iloc[i,0]
storage_discharge=(sorted24[-storage_time:].mean().values-(storage_amount/storage_time))[0]
# then discharge
for i in range(-storage_time,0):
sorted24.iloc[i,1]=storage_discharge-sorted24.iloc[i,0]
unsorted=sorted24.sort_index()
unsorted["after-storage"]=unsorted[0]+unsorted["storage"]
return unsorted;
Çalışan benzetici¶Yukarıdaki kodda piller şarj olmadan, sisteme elektrik verme durumları yaşadım. Ayrıca Python'un doğasından dolayı verilerin birbirine kopyalanırken sadece değer değil referansların da taşınması sorunu ile, herşeyi baştan ve test ederek yazmak zorunda kaldım. Çok daha karışık oldu ama stabilitesi nisbeten daha iyi. Bu kod 24 saatlik dilimler halinde
Kod karışık gelebilir. Eğer tek parametre girilecek ise sadece 24 saatlik talep verisi (pandas.DataFrame) olarak girilmesi yeterlidir. Fonksiyon otomatik olarak depolama miktarını 4000 MWh ve şarj-deşarj sürelerini 4 saat olarak alacaktır. In [4]:
def storage_player(df,storage_amount=4000, storage_time=4):
# depolama süresini arttırdıkça stabilite sorun yaşıyor
# if storage_time>7: storage_time=6
original=pd.DataFrame(df.values)
#en düşük talepten en yüksek talebe doğru sırala
sorted24=pd.DataFrame(df.values).sort_values(by=0,ascending=True)
#sıralamayı bir değişkene al
indextmp=sorted24.index.values
syc=0 # 3 defa sorun görünce çık, sayacı şimdi sıfırla
# eğer en düşük talep saatlerinden biri en yüksek talep saatlerinden birinden yüksek değerde ise, sorun var demektir.
# Şarj olmadan deşarj oluyor demektir. Bunu engellemek için aşağıdaki kod yazıldı
# bu aykırı saatleri en düşük ve en yüksek talep değerleri içinden siliyor
while max(indextmp[0:storage_time])>min(indextmp[-storage_time:]):
if max(indextmp[0:storage_time])> average(indextmp[-storage_time:]):
maxdeger=max(indextmp[0:storage_time])
tmp=[]
for i in range(0,len(indextmp)):
if indextmp[i]==maxdeger:
;
else:
tmp.append(indextmp[i]);
indextmp=tmp
if min(indextmp[-storage_time:])< average(indextmp[0:storage_time]):
mindeger=min(indextmp[-storage_time:])
tmp=[]
for i in range(0,len(indextmp)):
if indextmp[i]==mindeger:
;
else:
tmp.append(indextmp[i]);
indextmp=tmp
syc=syc+1
if syc>3: break; # 3 defa sorunda çık
minvalues=indextmp[0:storage_time] # en düşük talep saatleri
maxvalues=indextmp[-storage_time:] # en yüksek talep saatleri
#print(minvalues,"- -",maxvalues)
unsorted=sorted24.sort_index()
storage_up2=(original.iloc[minvalues].mean().values[0]+(storage_amount/storage_time))
storage_discharge=(original.iloc[maxvalues].mean().values[0]-(storage_amount/storage_time))
# ikinci sorun ise talebin düşük olduğu saatler şarj ile dolmayacak kadar büyük ise
# şarj süresini düşür
if (storage_up2-original.loc[0,0])<0:
storage_time=storage_time-1
minvalues=indextmp[0:storage_time]
maxvalues=indextmp[-storage_time:]
unsorted=sorted24.sort_index()
storage_up2=(original.iloc[minvalues].mean().values[0]+(storage_amount/storage_time))
storage_discharge=(original.iloc[maxvalues].mean().values[0]-(storage_amount/storage_time))
# değişkenleri hesapla ve original verisine yazarak fonksion çıktısı ver
for i in range(0,24):
original.loc[[i],"storage"]=0
original.loc[[i],"after_storage"]=0
if i in minvalues:
original.loc[[i],"storage"]=storage_up2-original.loc[[i],0]
if i in maxvalues:
original.loc[[i],"storage"]=storage_discharge-original.loc[[i],0]
original.loc[[i],"after_storage"]=original.loc[[i],0]+ original.loc[[i],"storage"]
# hala sorun varsa ekrana yansıt
if original.loc[0,"storage"]<0:
#print ("Sorted=",sorted24)
#print ("Original=",original)
print("Min values=",minvalues)
print("Max values",maxvalues)
return original;
Enerji depolama benzetisinin çalıştırılması¶Bir enerji depolama benzeticisini programladıktan sonra 365 gün için 24 saatlik dilimler (gece 00:00'dan 23:59'a kadar) halinde programı çalıştırabiliriz:
Program çıktılarını her aldığımıza, orjinal veri seti olan prices 'a storage ve after_storage olarak ekliyoruz
In [5]:
for i in range(0,365):
print(i,end=" ")
sonuc=storage_player(prices.loc[(0+i*24):(23+i*24),'genel_toplam'])
# çıkan değişkenleri prices'a ekle
for j in range(0,24):
prices.loc[j+i*24,"storage"]=sonuc.iloc[j,1]
prices.loc[j+i*24,"after_storage"]=sonuc.iloc[j,2]
İlk sonuçlar¶Türkiye elektrik sisteminden 2018'deki saatlik tüketimler ('genel_toplam' lisanssız dahil veriler) baz alındı. Sonuçlar veri dosyasının en sonuna 2 sütun halinde eklendi. Bunu prices.loc çağrısı ile yaptık. Şimdi verimizi kontrol edelim. Bu veride sorun olmasa da, (örneğin 1000 MWh, 4 saatte) sorunlar olmaktadır. Basit bir sebepten, eğer saatten saate yük değişimi depolama miktarından yüksek ise program şaşmaktadır. Şarj etmediği tüketimi sisteme sunmaktadır, bunu da ileri ki saatlerde borç bakiyesi gibi kullanmaktadır. Bunu düzeltmek için tekrar bir sürü kontrol yazmak gerekebilir. Önerilere de açığım. Düzeltmek için en kolay yol
In [6]:
prices.iloc[0:23,-5:]
Out[6]:
Yukarıda da görüldüğü üzere sabah 04:00 ile 06:00 arası şarj ettiği elektriği akşam 18:00 ile 20:00 arası vermektedir Grafikleme¶Grafikler için basit bir fonksiyon yazacağız, fonksiyon hangi günden başlayarak kaç günlük grafiği ve grafik başlığını soracak. Böylelikle tek fonksiyonlar sonraki grafikleri de yapabileceğiz. Grafikte,
In [7]:
def storage_graph(start_day, how_many_days, g_title):
gun=start_day
gunsayisi=how_many_days
basla=0+(gun-1)*24
sure=24*gunsayisi-1
uzun=basla+sure
plt.stackplot(prices.index[basla:uzun], prices.genel_toplam[basla:uzun],prices.storage[basla:uzun], labels=['MW-Tüketim','MW-depolama'])
plt.plot(prices.genel_toplam[basla:uzun],color="red",linewidth=4)
plt.plot(prices.after_storage[basla:uzun],color="black", linewidth=4)
plt.legend(loc='best')
ylim(min(prices.genel_toplam[basla:uzun])*0.95,max(prices.genel_toplam[basla:uzun])*1.05)
title(g_title)
In [8]:
storage_graph(1,5,"4000 MWh Depolama etkisi")
Tertiplenmiş yük eğrisindeki değişim¶Elektrik sistemindeki en meşhur eğrilerden biri tertiplenmiş yük eğrisidir. Kısaca tüm sene boyunca tüm saatlerdeki tüketimi en yüksekten en düşüğe sıralayarak grafiklenir. 8760 saat için en yüksek ve en düşük talep saatlerini rahatça görebilmemizi sağlar In [9]:
plot(prices.genel_toplam.sort_values().values)
plot(prices.after_storage.sort_values().values)
title("Tertiplenmiş yük eğrisi - 4000 MWh");
Tertiplenmiş yük eğrisinde büyük bir değişim görülmemektedir. Ama ortalama 800000 MWh tüketim olsa, günlük tüketimin sadece %0.5'i depolamaya konu olacağı için büyük bir etkisinin olmasını beklemiyorduk. Fakat eğrinin iki ucuna bakmak faydalı olabilir. Önce alt ucuna yani düşük talep dönemlerine bakalım. Bu dönemler baz yük santrallerini zorlayan dönemler. In [10]:
plot(prices.genel_toplam.sort_values().values[1:100])
plot(prices.after_storage.sort_values().values[1:100])
Out[10]:
Bir de eğrinin en yüksek taleplerin görüldüğü dönemine bakalım. Yani en yüksek talebin olduğu 100 saat In [11]:
plot(prices.genel_toplam.sort_values().values[8660:])
plot(prices.after_storage.sort_values().values[8660:])
Out[11]:
Bazı istatistik veriler¶Peki elektrik sistemimizdeki talep dağılımı bundan nasıl etkilendi? Bunun için de standart sapmaya bakmakta fayda var:
In [12]:
prices.genel_toplam.std()
Out[12]:
In [13]:
prices.after_storage.std()
Out[13]:
Görüldüğü üzere sisteme 4000 MWh'lik bir depolama eklendiği zaman sistemin değişkenliği azalmaktadır. In [14]:
hist(prices.after_storage, bins=100, density=1,label="Depolama sonrası");
hist(prices.genel_toplam, bins=100, density=1,label="Orjinal Talep");
plt.legend(loc="best");
Depolama hangi kaynakla daha iyi çalışıyor¶Bu kısım tartışmalı da olsa, korelasyon katsayılarını görmekte yarar var. Hepimiz güneş ve depolama, elektrik sisteminin geleceği gibi düşünürken, veriler daha farklı bir şey söylüyor. Teknik bir tartışma olacağı için detaya girmeyeceğim. Ama muhtemelen depolama sistemdeki yenilenebilir seviyesine göre bir dengeleyici oyuncu olacaktır. Aynı zamanda baz yüke de katkı sağlayacaktır In [15]:
prices[["dogalgaz","linyit","barajli","ithalkomur","akarsu","l_gunes","ruzgar","storage"]].corr().iloc[:,-1]
Out[15]:
Bir sonraki aşama¶Farzedelim ki sistemde depolama çok ucuzladı ve 40000 MWh'lik bir depolama kapasitesi oldu. Bu 5000 MW 8 Saat'e denk geldiği gibi, 10000MWh 4 saat de olabilir. Bu hala Türkiye elektrik sisteminin günlük tüketiminin %5'inden fazlasına denk gelmemektedir. Şimdi benzeticimizi ve tüm grafikleri bir de bu parametreler ile çalıştıralım. 365 gün için her 24 saatte 40000 MWh'in sisteme etkisini görelim. In [16]:
for i in range(0,365):
print(i,end=" ")
sonuc=storage_player(prices.loc[(0+i*24):(23+i*24),'genel_toplam'],storage_amount=40000, storage_time=8)
# çıkan değişkenleri prices'a ekle
for j in range(0,24):
prices.loc[j+i*24,"storage"]=sonuc.iloc[j,1]
prices.loc[j+i*24,"after_storage"]=sonuc.iloc[j,2]
Örnek olarak yaz ayında bir haftalık tüketimde depolamanın etkisini görelim. Sonuçla mükemmel değil ama ekstra kötü de değil In [17]:
storage_graph(203,7,"40000 MWh Depolama")
Tertiplenmiş yük eğrimizde, ise %5 depolama ile tüm eğrinin biraz daha ortalamaya geldiğini görüyoruz. En yüksek talep saatlerinde neden sivrilik gitmedi diyebilirsiniz. İki sebebi olabilir:
In [18]:
plot(prices.genel_toplam.sort_values().values, label="Toplam talep")
plot(prices.after_storage.sort_values().values, label="Depolama sonrası")
plt.legend(loc='best')
title("Tertiplenmiş yük eğrisi - 40000 MWh");
En yüksek talebin olduğu 100 saate bakalım In [19]:
plot(prices.genel_toplam.sort_values().values[8660:])
plot(prices.after_storage.sort_values().values[8660:])
Out[19]:
Bir de korelasyonların durumunu görelim In [20]:
prices[["dogalgaz","linyit","barajli","ithalkomur","akarsu","l_gunes","ruzgar","storage"]].corr().iloc[:,-1]
Out[20]:
Histogramlar ise depolamanın etkisi ile daha ortalamanın etrafında dağılan bir elektrik sistemi göstermektedir. Uçlar neredeyse kaybolmuş, tüm tüketim neredeyse saatlik olarak 30000 MW ile 40000 MW arasında dizilmektedir. In [21]:
hist(prices.genel_toplam, bins=100, density=1,label="Orjinal Talep");
hist(prices.after_storage, bins=100, density=1,label="Depolama sonrası");
plt.legend(loc="best");
Sonuç ve Teknik tartışma¶Önce sorunlar:
Depolama sisteminin Türkiye elektrik sistemini nasıl etkileyeceğini bilmiyoruz. Basit simulasyonlar da tüm etkileri göstermez. Ama ucuz bilgisayar gücü ile fikir veren benzetimler yapılabilir. Bu çalışma da bunlardan biridir. Barış Sanlı, 31 Mart 2019, (barissanli2@gmail.com) |