Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
A cura di:
1
2. Implementazione del decisore
Obiettivo
L’obiettivo di questo lavoro è di realizzare, tramite il programma Matlab, un sistema intelligente in
grado di presentare automaticamente offerte a nome di un produttore. Con Simulink ed in particolare
Stateflow, è stato realizzato un modello in grado di simulare una sessione di mercato. Il produttore
propone una propria offerta, sia in termini di energia che di prezzo, suddivisa in due blocchi, dove il
primo presenta un prezzo minore rispetto all’altro blocco ed una quantità maggiore per avere la
certezza di vendita di una buona quota dell’energia.
La strategia adottata dal produttore è la seguente: inizialmente propone un’offerta (composta da due
coppie quantità-prezzo), in seguito coi risultati alla mano della prima sessione di mercato decide se e
di quanto modificare l’offerta a seconda che essa sia stata accettata totalmente, parzialmente o per
niente. Soltanto dopo la seconda sessione di mercato il produttore si potrà regolare, anche
considerando il guadagno che ha ottenuto e alla percentuale di vendita dei propri blocchi.
Strumenti del progetto
Verrà realizzato il progetto con ausilio del software Matlab®, un ambiente per il calcolo numerico e
l'analisi statistica scritto in C, che comprende anche l'omonimo linguaggio di programmazione creato
dalla MathWorks, tramite la programmazione di una serie di comandi, contenuti in un file testo (m-
file), con l’utilizzo dell’editor, finestra predisposta a tale funzione. L’m-file, costruito appositamente,
è di tipo script e si differenzia da un tipo function perché tutte le variabili contenute sono già definite
in Matlab (variabili globali) e non nascono e muoiono in esso. Questo file di testo, eseguendolo,
simulerà il mercato elettrico d’interesse e stamperà sullo schermo una serie di grafici che descrivono
le variabili d’interesse e il risultato finale.
Inoltre, oltre alla programmazione dell’m-file, è necessario avere come aiuto l’ambiente Simulink, in
particolar modo l’utilizzo di Stateflow.
Simulink è uno strumento integrato con Matlab per modellazione, simulazione ed analisi di sistemi
dinamici. Sarà possibile rappresentare con molta semplicità un sistema complesso tramite
interconnessioni di sottosistemi. Stateflow invece è uno strumento di progetto e sviluppo di sistemi
di supervisione all’interno di Simulink tramite creazione grafica di sistemi. Permette di rappresentare
in modo conciso il comportamento di sistemi complessi, usando un approccio State-Driven
(esecuzioni di determinate azioni sul processo in base allo stato di evoluzione del processo dipendente
dalla sequenza degli ingressi che si sono presentati).
Infine bisogna scaricare ed implementare in Matlab un pacchetto di m-file disponibile online
chiamato Matpower. Il pacchetto, sviluppato inizialmente come parte del progetto PowerWeb, è
inteso come uno strumento di simulazione per ricercatori e studenti facile da modificare ed usare per
risolvere problemi come il flusso di potenza ottimale o il mercato elettrico.
2
3. Decisore
Simulink
In ambiente Simulink, attraverso Stateflow, è stato realizzato uno strumento in grado di verificare se
l’offerta presentata nella precedente sessione di mercato è stata accettata completamente,
parzialmente oppure non è stata accettata. In base a ciò il decisore stabilisce i prezzi della sessione
successiva, basandosi sul ΔP, ovvero la differenza tra il market clearing price e il prezzo da noi
proposto.
Nel seguente schema sono illustrate le variabili di input e output del nostro decisore.
3
OUTPUT
Pout1: prezzo del primo blocco da offrire per la sessione successiva;
Pout2: prezzo del secondo blocco da offrire per la sessione successiva;
Tali variabili vanno poi esportate in Matlab per cui si usa il blocco “To Workspace” in Simulink.
Stateflow
La logica su cui si basa il decisore è invece stata implementata attraverso Stateflow:
2.1. Passo 1
Al passo 1, la variabile “t” vale 1, per cui è verificata la condizione 1. L’offerta iniziale sarà proprio
quella che noi abbiamo scelto prima di iniziare la simulazione.
4
2.2. Passo 2
Al passo 2 la variabile “t” è pari a 2, per cui si verifica la condizione 2. Si presentano adesso tre
possibili casi:
Offerta parzialmente accettata: questo caso si verifica quando il Poff1 è minore o uguale del
MCP, per cui l’offerta relativa al primo blocco viene accettata, mentre Poff2 è maggiore del
MCP, quindi l’offerta relativa al secondo blocco viene rifiutata. In questo caso decido di
uguagliare Poff2 al MCP, per avere più possibilità che nella sessione successiva venga
accettata, mentre lascio invariato Poff1, poiché molto probabilmente quest’offerta sarà
accettata anche nella prossima sessione.
Offerta totalmente accettata: questo caso si verifica quando sia Poff1 che Poff2 sono minori
del MCP. Decido quindi di porre Poff1 leggermente al di sotto del MCP, uguagliandolo a
MCP-D2/2 (scelgo D2 poiché in questo caso D2<D1, così facendo non mi allontano troppo
dal MCP); pongo poi Poff2 pari proprio al MCP.
Offerta non accettata: l’ultimo caso si verifica se sia Poff1 che Poff2 sono maggiori del MCP,
in questo caso D2>D1. Devo fare in modo che nella prossima sessione almeno un’offerta
venga accettata, di conseguenza decido di porre Poff1 al di sotto del MCP (Poff1=MCP-D2)
e Poff2 leggermente al di sotto del MCP (Poff2=MCP-D1/3)
5
2.3. Passo 3 e successivi
Al passo 3, così come nei successivi, la variabile t risulterà maggiore o uguale di 3, per cui si verifica
la condizione 3. Il decisore a questo punto esamina la differenza G tra i ricavi derivanti dalla sessione
appena conclusa e quelli derivanti dalla sessione precedente. Se tale differenza è positiva, vuol dire
che la strategia che si è perseguita è giusta e quindi i prezzi vengono calcolati nello stesso modo. Se
invece G dovesse essere negativa, allora si distinguono nuovamente tre possibilità.
Offerta parzialmente accettata: riduco Poff1 e pongo Poff2 pari al MCP per cercare di vendere
tutto il quantitativo energetico disponibile ed aumentare i ricavi;
Offerta totalmente accettata: in questo caso dobbiamo cercare di portare il MCP ad un valore
superiore perché, pur essendo l’offerta precedente totalmente accettata, i ricavi sono diminuiti.
Per questo motivo, poniamo Poff1 leggermente al di sotto del MCP, mentre poniamo Poff2 al
di sopra.
Offerta non accettata: nessuna delle due offerte è stata accettata nella sessione precedente e
quindi i ricavi sono diminuiti. Dobbiamo fare in modo da far accettare le nuove offerte, che
per tale motivo verranno poste al di sotto del MCP.
6
4. Rete utilizzata
All’interno del package di casi è stata scelta la rete denominata “case 9” a cui sono state apportate le
necessarie modifiche per soddisfare le esigenze progettuali. Tale rete presenta le seguenti
caratteristiche:
Numero di nodi: 9
Numero di generatori: 5
Numero di consumatori: 4
% MATPOWER
% $Id: case9.m 1559 2010-03-10 18:08:32Z ray $
%% bus data
% bus_i type Pd Qd Gs Bs area Vm Va baseKV zone Vmax Vmin
mpc.bus = [
1 3 0 0 0 0 1 1 0 345 1 1.1 0.9;
2 2 0 0 0 0 1 1 0 345 1 1.1 0.9;
3 2 0 0 0 0 1 1 0 345 1 1.1 0.9;
4 1 0 0 0 0 1 1 0 345 1 1.1 0.9;
5 1 0 0 0 0 1 1 0 345 1 1.1 0.9;
6 1 0 0 0 0 1 1 0 345 1 1.1 0.9;
7 2 0 0 0 0 1 1 0 345 1 1.1 0.9;
8 2 0 0 0 0 1 1 0 345 1 1.1 0.9;
9 2 0 0 0 0 1 1 0 345 1 1.1 0.9];
%% generator data
% bus Pg Qg Qmax Qmin Vg mBase status Pmax Pmin Pc1 Pc2 Qc1min
Qc1max Qc2min Qc2max ramp_agc ramp_10 ramp_30 ramp_q apf
mpc.gen = [
1 160 0 60 -60 1 100 1 1000 0;
2 200 0 60 -60 1 100 1 1000 0;
3 150 0 60 -60 1 100 1 1000 0;
4 200 0 60 -60 1 100 1 1000 0;
5 250 0 60 -60 1 100 1 1000 0;
6 -1 0 0 0 1 100 1 0 -1000;
7
7 -1 0 0 0 1 100 1 0 -1000;
8 -1 0 0 0 1 100 1 0 -1000;
9 -1 0 0 0 1 100 1 0 -1000];
%% branch data
% fbus tbus r x b rateA rateB rateC ratio angle status
angmin angmax
mpc.branch = [
1 4 0 0.0576 0 250 250 250 0 0 1 -360 360;
4 5 0.017 0.092 0.158 250 250 250 0 0 1 -360 360;
5 6 0.039 0.17 0.358 150 150 150 0 0 1 -360 360;
3 6 0 0.0586 0 300 300 300 0 0 1 -360 360;
6 7 0.0119 0.1008 0.209 150 150 150 0 0 1 -360 360;
7 8 0.0085 0.072 0.149 250 250 250 0 0 1 -360 360;
8 2 0 0.0625 0 250 250 250 0 0 1 -360 360;
8 9 0.032 0.161 0.306 250 250 250 0 0 1 -360 360;
9 4 0.01 0.085 0.176 250 250 250 0 0 1 -360 360;
];
8
5. Codice di simulazione
Al fine di avere una simulazione quanto più realistica possibile è necessario basarsi su dati reali per
cui sono state prelevate delle informazioni dal gestore della rete relativamente a quantità e prezzi.
Partendo da queste informazioni sono stati creati due vettori di dati normalizzati rispetto al massimo
di ciascun vettore.
%Vettore delle quantità di corrente, definite come rapporto tra la quantità i-
esima e la quantità massima
Vett_qty=[0.58 0.55 0.53 0.54 0.57 0.61 0.76 0.89 0.95 0.98 0.98 0.97 0.89 0.90
0.93 0.92 0.94 1 0.98 0.95 0.87 0.78 0.70 0.64];
Figura 7 – Andamento delle quantità richieste e dei prezzi nel corso della giornata
9
La rete è stata richiamata nel codice di simulazione attraverso la funzione “loadcase”:
mpc=loadcase('market_case9');
%Dal momento che il mercato si svolge sulle 24 ore, creiamo un indice che
%va da 1 a 24
x=[1:1:24];
Possiamo adesso passare alla definizione delle offerte di vendita e di acquisto, dette rispettivamente
offers e bids.
% Matrici delle offerte di vendita e di acquisto:
In realtà questa matrice non viene definita adesso, è riportata solo per completezza. La matrice delle
offerte di vendita viene definita di volta in volta nel ciclo for che costruiremo successivamente.
Notiamo che la matrice delle offerte di vendita è incompleta, mancano infatti i prezzi relativi al prima
produttore. Tali prezzi saranno infatti determinati dal decisore, e cambieranno in ogni sessione del
Mercato del Giorno Prima.
%Matrice delle quantità di acquisto dei compratori
b.P.qty=[
60 60;
100 0;
80 20;
50 30];
%Matrice dei prezzi delle offerte di acquisto dei compratori
b.P.prc=[
150 50;
100 0;
110 80;
120 60];
10
Possiamo quindi riportare sui grafici le diverse offerte.
11
12
Una volta definite le offerte di vendita e di acquisto, i vettori delle quantità e dei prezzi, possiamo
passare alla scrittura del codice che definisce lo svolgimento della simulazione del mercato.
Innanzitutto è necessario definire le variabili globali che costituiranno le variabili di input per il
decisore implementato in Stateflow.
%Definisco le variabili globali che usa il decisore di Stateflow, che hanno
%26 componenti per permettere il calcolo al decisore nelle prime due
%iterazioni
global t Poff1 Poff2 mcp Rtot RtotPrec D1 D2 D
È necessario poi creare dei vettori di appoggio relativi alle quantità accettate, al MCP, ai prezzi
accettati delle offerte e ai ricavi totali. Questi vettori hanno 26 componenti in modo tale da permettere
al decisore di effettuare i primi due passi prima dell’inizio della simulazione vera e propria.
%vettori di appoggio
Qacc1_vett=zeros(1,26);
Qacc2_vett=zeros(1,26);
mcp_vett=zeros(1,26);
Poff1_vett=zeros(1,26);
Poff2_vett=zeros(1,26);
Rtot_vett=zeros(1,26);
Per effettuare una simulazione completa del Mercato del Giorno Prima, le operazioni vanno ripetute
per 24 volte, una volta per ogni ora del giorno, attraverso un ciclo for. Dopo aver inizializzato il ciclo
for vanno definiti i ricavi e i vettori che costituiranno gli input per il decisore precedentemente
implementato in Stateflow.
%il ciclo for deve reiterare le stesse operazioni 24 volte, una per ogni
%ora del giorno
for t_m=1:24
t=[1, t_m];
D1_m=(abs(mcp_vett(t_m+1)-Poff1_vett(t_m))+1)/2;
13
D2_m=(abs(mcp_vett(t_m+1)-Poff2_vett(t_m))+1)/2;
D_m=(D1_m+D2_m)/2;
D1=[1, D1_m];
D2=[1,D2_m];
D=[1, D_m];
Una volta definite tutti gli input possiamo richiamare il decisore attraverso la funzione “sim”; per
farlo funzionare correttamente vanno definiti gli estremi temporali della simulazione, che erano stati
definiti prima del ciclo for.
%Simulazione con stateflow
sim('decisoreCM.slx',[t_iniz t_fin]);
Possiamo quindi elaborare gli output di Stateflow, prima però è necessario effettuare una media tra i
diversi valori che il decisore ha restituito ed arrotondare la media, in modo tale da non portarci dietro
troppe cifre decimali. Il decisore effettua infatti più simulazioni e pertanto va fatta una media tra i
valori derivanti dalle diverse simulazioni. Gli output che elaboreremo sono, come detto
precedentemente, i prezzi delle offerte di vendita dei nostri due blocchi di energia.
%Effettuo una media tra i diversi valori e la arrotondo alla prima cifra decimale
%con "round"
[r1,c1]=size(Pout1.signals.values);
[r2,c2]=size(Pout2.signals.values);
Poff1_m=round(10*Pout1.signals.values(r1,1))/10;
Poff2_m=round(10*Pout2.signals.values(r2,1))/10;
Poff1_vett(t_m+1)=Poff1_m;
Poff2_vett(t_m+1)=Poff2_m;
Inseriti i prezzi delle nostre offerte di vendita nella matrice “offers.P.prc”, ovvero la matrice dei
prezzi dei venditori, abbiamo a disposizione tutte le matrici complete. Pertanto è possibile svolgere il
Mercato del Giorno Prima attraverso la funzione “runmarket”. Tale funzione richiede in input: mpc,
ovvero la rete su cui basiamo la simulazione, nel nostro caso la rete era “market_case9”, offers e bids,
cioè le matrici di quantità e prezzi dei produttori e degli acquirenti, e mkt, in cui sono contenuti i
parametri del nostro mercato che erano stati assegnati precedentemente, corrente alternata e mercato
LAO (Last Accepted Offer). Tra gli output di questa funzione ci interessano: mpc_out, ovvero il
prezzo di equilibrio nelle 24 ore, dispatch, che indica la potenza dispacciata, f, che rappresenta il
social welfare, co e cb, cioè le offerte accettate per quanto riguarda rispettivamente produttori e
consumatori.
%Svolgimento sessione di mercato attraverso la funzione "runmarket"
[mpc_out, co,cb,f,dispatch,success,et]=runmarket(mpc,offers,bids,mkt);
Qacc1_vett(t_m+2)=co.P.qty(1,1);
Qacc2_vett(t_m+2)=co.P.qty(1,2);
mcp_vett(t_m+2)=round(10*co.P.prc(1,1))/10;
14
A questo punto possiamo raccogliere le informazioni che ci servono all’interno di appositi vettori,
che verranno riportati su dei grafici. In particolare ci interessano: gli andamenti di MCP, Prezzo
dell’offerta 1 e Prezzo dell’offerta 2, i ricavi derivanti dalla vendita di ciascuno dei due blocchi di
energia e il ricavo totale.
%vettori per i grafici
Poff1vett(t_m)=Poff1_m;
Poff2vett(t_m)=Poff2_m;
MCPvett(t_m)=mcp_vett(t_m+2);
Ricavo1(t_m)= mcp_vett(t_m+2)*Qacc1_vett(t_m+2);
Ricavo2(t_m)= mcp_vett(t_m+2)*Qacc2_vett(t_m+2);
RicavoTot(t_m)= Ricavo1(t_m)+Ricavo2(t_m);
RicavoCum(t_m)=sum(RicavoTot);
end
15
6. Grafici
L’ultima parte del codice è quella relativa ai comandi per la rappresentazione grafica.
Riportiamo innanzitutto il grafico relativo all’andamento del fabbisogno nel corso della giornata.
Vediamo quindi come il picco di richiesta sia alle ore 18, mentre valori comunque elevati si
riscontrano nelle ore mattutine. Tra le 13 e le 17 c’è una lieve flessione della domanda, che invece
cala drasticamente dalle 21 in poi, per raggiungere il minimo alle 3 di notte. Dalle 7 di mattina, con
la ripresa delle attività, cresce nuovamente il fabbisogno energetico.
Quest’andamento rispecchia chiaramente le abitudini della popolazione, ed era pertanto di facile
previsione.
16
Con un primo grafico vogliamo rappresentare contemporaneamente gli andamenti del MCP, del
prezzo relativo al primo blocco e al secondo nel corso delle diverse ore del giorno.
figure (1)
plot(x,MCPvett,'r')
hold on
plot(x,Poff1vett,'y')
plot(x,Poff2vett,'b')
grid on
xlabel('Tempo')
legend('MCP','Poff1','Poff2')
axis([1 24 35 75])
hold off
Da questo grafico vediamo come fossimo partiti con un prezzo del primo blocco basso, e un prezzo
del secondo blocco alto; tali prezzi si sono rapidamente allineati al MCP, già dalla quarta ora infatti i
prezzi si mostrano in linea con il mercato. Seguiranno poi l’andamento del MCP, trovandosi, come
desiderato, quasi sempre di poco al di sotto del MCP, in modo tale da garantire l’accettazione delle
offerte.
17
Col successivo grafico verifichiamo proprio l’andamento delle quantità accettate del primo e del
secondo blocco di energia elettrica.
figure (2)
plot(Qacc1_vett,'r')
hold on
plot(Qacc2_vett,'y')
grid on
xlabel('Tempo')
legend('Quantità 1','Quantità 2')
hold off
Come giustamente dedotto dall’andamento dei prezzi rispetto al MCP, il primo blocco viene venduto
sempre interamente, ad eccezione di un’ora, le 22, che come abbiamo visto corrisponde all’orario in
cui diminuisce fortemente il fabbisogno di energia elettrica. Il secondo blocco viene anch’esso
venduto interamente nella maggior parte delle sessioni, ad eccezione di qualche ora pomeridiana in
cui c’è una lieve flessione di richiesta di energia elettrica. Alle 22 la seconda offerta viene
completamente rifiutata, e questo deriva dalla forte diminuzione di richiesta. Il decisore è però molto
reattivo nell’adattare i prezzi al MCP, e infatti, nonostante l’esigua domanda nelle ore notturne,
riusciamo comunque a vendere per intero sia il primo che il secondo blocco di energia.
18
Il terzo grafico è costruito al fine di visualizzare la variazione del ricavo rispetto a quelle dell’MCP e
del fabbisogno.
figure (3)
plot (x,Vett_qty*100,'r')
hold on
plot(x,MCPvett,'y--')
plot(x,RicavoTot/100,'b')
legend('Fabbisogno','MCP','Ricavo')
ylabel('Variazioni')
xlabel('Tempo')
grid on
hold off
Dal grafico vediamo come ovviamente il nostro ricavo dipenda fortemente dal fabbisogno del mercato
di energia elettrica. L’efficienza del decisore costruito si evince dalle ultime due ore in cui, nonostante
una forte diminuzione della richiesta, riusciamo a conseguire un ricavo in linea con quello ottenuto
nelle ore di picco di domanda di energia.
19
Nel quarto grafico possiamo vedere quanto vale il ricavo totale dopo ogni sessione del mercato del
giorno prima.
figure(4)
plot(x,RicavoCum,'r')
xlabel('Tempo')
grid on
ylabel('Ricavo cumulato')
axis([1 24 0 250000])
A fine giornata il ricavo totale risulta essere pari a 198 670 €. Questo è un dato molto buono ed è
dovuto al fatto che nella maggior parte delle sessioni di mercato siamo riusciti a vendere tutta
l’energia prodotta.
20
Infine con l’ultimo grafico si vuole dare una visione d’insieme, mostrando contemporaneamente le
variazioni di fabbisogno, MCP, ricavo e prezzi delle singole offerte.
figure (5)
plot (x,Vett_qty*100,'r')
hold on
plot(x,MCPvett,'y--')
plot(x,RicavoTot/100,'b:')
plot(x,Poff1vett,'g')
plot(x,Poff2vett,'c')
legend('Fabbisogno','MCP','Ricavo','Poff1','Poff2')
ylabel('Variazioni')
xlabel('Tempo')
grid on
hold off
21