Additional Supporting Material for paper: “Optimizing wellfield operation in a variable power price regime” by Peter Bauer-Gottwein, Raphael Schneider and Claus Davidsen Abstract: Wellfield management is a multi-objective optimization problem. One important objective has been energy efficiency in terms of minimizing the Energy Footprint (EFP) of delivered water (MWh/m3). However, power systems in most countries are moving in the direction of deregulated markets and price variability is increasing in many markets because of increased penetration of intermittent renewable power sources. In this context the relevant management objective becomes minimizing the cost of electric energy used for pumping and distribution of groundwater from wells rather than minimizing energy use itself. We estimated energy footprint of pumped water as a function of wellfield pumping rate (EFP-Q relationship) for a wellfield in Denmark using a coupled well and pipe network model. This EFP-Q relationship was subsequently used in a Stochastic Dynamic Programming (SDP) framework to minimize total cost of operating the combined wellfield-storage-demand system over the course of a 2-year planning period based on a time series of observed price on the Danish power market and a deterministic, time-varying hourly water demand. In the SDP setup, hourly pumping rates are the decision variables. Constraints include storage capacity and hourly water demand fulfilment. The SDP was solved for a baseline situation and for five scenario runs representing different EFP-Q relationships and different maximum wellfield pumping rates. Savings were quantified as differences in total cost between the scenario and a constant-rate pumping benchmark. Minor savings up to 10% were found in the baseline scenario, while the scenario with constant EFP and unlimited pumping rate resulted in savings up to 40%. Key factors determining potential cost savings obtained by flexible wellfield operation under a variable power price regime are the shape of the EFP-Q relationship, the maximum feasible pumping rate and the capacity of available storage facilities. MATLAB code listing for the water value method %% Inputs avdemand = 588; % average water demand, m3/hour wtcap = 47173*1.5; % storage capacity of the water tower, m3. Share of Tinghøj according to inflow sdisc = 500/2; %storage discretization (m^3) wdhourfactors = [2.4865313e-02 1.4504766e-02 1.0360547e-02 8.2884376e-03 1.0360547e-02 2.0721094e-02 3.9370079e-02 6.1334438e-02 7.0451720e-02 6.4235392e-02 6.0091173e-02 4.9730626e-02 4.7658516e-02 4.5586407e-02 4.3514298e-02 4.5586407e-02 4.7658516e-02 5.3874845e-02 6.0091173e-02 5.8019063e-02 4.7658516e-02 4.1442188e-02 3.9370079e-02 3.5225860e-02]; % from http://www2.mst.dk/common/Udgivramme/Frame.asp?http://www2.mst.dk/Udgiv/publikationer/2005/87-7614-5921/html/sum.htm wdhour = 24*wdhourfactors*avdemand; % hourly water demand, m3/hour nscen = 5; %Number of price classes %% Getting electricity price info elprice=xlsread('Elprice_2012_13.xlsx','Elprice','D4:D17547'); % from http://www.energinet.dk/DA/El/Engrosmarked/Udtraek-af-markedsdata/Sider/default.aspx % in DKK/MWh time1=datenum('01-01-2012 00:00:00'); time=time1+([1:1:17544]-1)/24; timevec = datevec(time); hour = timevec(:,4)+1; for i=1:1:24 pphour(i) = nanmean(elprice(find(hour==i))); stdhour(i) = nanstd(elprice(find(hour==i))); elpriceav(hour==i) = pphour(i); elpricestd(hour==i) = stdhour(i); end elprice(find(isnan(elprice)))=elprice(find(isnan(elprice))-1); %Filling in the few NaNs that there are elpricenorm = (elprice-elpriceav')./elpricestd'; %Normalizing qbreaks = [1:1:nscen-1]/nscen; breaks = quantile(elpricenorm,qbreaks); nclasses = length(breaks) +1; % Classifying price time series elpriceclass (elpricenorm<breaks(1))=1; nsamples(1) = sum(elpricenorm<breaks(1)); for i=2:1:nclasses-1 elpriceclass (elpricenorm>=breaks(i-1) & elpricenorm<breaks(i))=i; nsamples(i) = sum(elpricenorm>=breaks(i-1) & elpricenorm<breaks(i)); end elpriceclass (elpricenorm>breaks(end))=nclasses; nsamples(nclasses) = sum(elpricenorm>breaks(end)); % Finding mean price for each class and each hour for i=1:1:24 for j=1:1:nclasses pphourscen(j,i) = nanmean(elprice(find(hour==i & elpriceclass'==j))); end end % Finding transition probability matrices for each class and each hour for i = 1:1:24 for j = 1:1:nclasses for k = 1:1:nclasses count1 = sum(hour==i & elpriceclass' ==j); nextstep = find(elpriceclass' == j & hour == i) ; nextstep = nextstep(1:end-1) + 1; count2 = sum(elpriceclass(nextstep)==k); TP(j,k,i) = count2/(count1-1); end end end %% Energy footprint. % assumed to be a polynomial. Gives the energy footprint (kWh/m3) for given % pumping rate in m3/hour. This is the result of the Søndersø wellfield % model load('maxrate.mat'); %maximum abstraction rate for the wellfield in m3/hour maxrate = max(wdhour)+1; load('WFCHARpoly3.mat'); %Coefficients of the polynomial as produced by polyfit WFCHAR = WFCHARpoly3/3.6/1000; %Conversion from MJ/m3 to MWh/m3 qplot = linspace(0,maxrate,20); efpplot = polyval(WFCHAR,qplot); plotcc = 1; if plotcc plot(qplot,efpplot); xlabel('pumping rate, m^3/hour') ylabel('energy footprint, MWh/m^3') end %% Initializing S=[0:sdisc:wtcap]; maxS = max(S); expfutcost = 0*ones(nclasses,1)*S; expfutlambda = 0*ones(nclasses,1)*S; tcost = 0*ones(nclasses,1)*S; arlambda = 0*ones(nclasses,1)*S; %% Loop backwards figure;hold on; poolobj=parpool('Peter'); poolobj.NumWorkers for l=1:1:100 % This is typically enough to reach steady-state water values for j=24:-1:1 display(j); for k = 1:1:nclasses expfutcostscen = S*0; expfutlambdascen = S*0; for m = 1:1:nclasses expfutcostscen = expfutcostscen + TP(k,m,j) * expfutcost(m,:); expfutlambdascen = expfutlambdascen + TP(k,m,j) * expfutlambda(m,:); end parfor i=1:1:length(S) [ic(i) fc(i) tc(i) lambda(i)] = TC_function(S(i),maxS,wdhour(j),pphourscen(k,j),S,expfutcostscen,expfutlambdascen, maxrate, WFCHAR); end intermediatecost(k,:)=tc; intermediatelambda(k,:)=lambda; subplot(nclasses,1,k); hold on; plot(tc,S,'c','LineWidth',2) xlabel('total cost in DKK','FontSize',14) ylabel('Storage in m^3','FontSize',14) set(gca,'fontsize',14) end expfutcost = intermediatecost; expfutlambda = intermediatelambda; tcost = cat(3,intermediatecost,tcost); arlambda = cat(3,intermediatelambda,arlambda); end end delete(poolobj); plotvv = 1 if plotvv figure for i=1:1:nclasses subplot(nclasses,1,i) big = size(arlambda); wv = reshape(arlambda(i,:,1:end-1),big(2),big(3)-1); imagesc(flipud(wv)); set(gca,'YTick',[1:10:big(2)],'YTickLabel',fliplr(S(1:10:length(S)))); xlabel('Time, hours') ylabel('Storage volume, m^3'); hh=colorbar ylabel(hh,'water value, DKK/m^3'); end end %% Saving equilibrium water values and future costs for simulation phase clear wv expfutcost for i=1:1:nclasses big = size(arlambda); wv(:,:,i) = reshape(arlambda(i,:,2:25),big(2),24); expfutcost(:,:,i) = reshape(tcost(i,:,2:25),big(2),24); end save wvsdp.mat wv expfutcost arlambda tcost % shadow prices are for total cost from beginning of t to end of period. % However, we need end to t to end of period for simulation phase. %% Simulation phase real prices elprice(find(isnan(elprice)))=elprice(find(isnan(elprice))-1); %replacing the very few NaN's that there are in the elprice time series efpav = polyval(WFCHAR,avdemand); totcostconst = nansum(elprice*efpav*avdemand); %This is the cost when pumping at a constant rate Scurrent = wtcap/2; clear ic fc tc lambda x for i=1:1:length(elprice) class = elpriceclass(i); expfutcostscen = S*0; expfutlambdascen = S*0; for m = 1:1:nclasses expfutcostscen = expfutcostscen + TP(class,m,hour(i)) * expfutcost(:,hour(i),m)'; expfutlambdascen = expfutlambdascen + TP(class,m,hour(i)) * wv(:,hour(i),m)'; end [ic(i) fc(i) tc(i) lambda(i) x(:,i)] = TC_function(Scurrent,maxS,wdhour(hour(i)),elprice(i),S,expfutcostscen,expfutlambdascen, maxrate, WFCHAR); display(i) Scurrent = x(2,i); end totcostvar = sum(ic); qvar = x(1,:); svar = x(2,:); save policysdp.mat totcostconst totcostvar qvar svar function [ic,fc,tc,lambda,x] = TC_function(S,maxS,wdhour,pphour,Sf,FC,lambda,upperq, polycoeff) %Define objective function: function obj = ofunction(x) obj=pphour*x(1)*polyval(polycoeff,x(1))+x(3); end % Constraints on future cost alpha = -lambda; beta=Sf; gamma=FC; ncuts = length(alpha); A = [zeros(ncuts,1) alpha' -ones(ncuts,1)]; b = alpha'.*beta' - gamma'; %Define water balance equality constraint: Aeq = [-1 1 0]; beq = S - wdhour; %Define lb: lb=[0;0;0]; ub = [upperq;maxS;Inf]; %Run constrained optimization options = optimset('Algorithm','interior-point','TolX',1E-12,'TolCon',1E-8,'TolFun',1E8,'Diagnostics','off','Display','off'); [x,fval,exitflag,output,lambda] = fmincon(@ofunction,[0;S;FC(find(abs(Sf-S)==min(abs(SfS))))],A,b,Aeq,beq,lb,ub,[],options); if ~(exitflag==1 | exitflag==2) % catching non-convergence exitflag keyboard end %Compute immediate, future and total cost in optimal solution ic = fval - x(3); fc = x(3); tc = fval; lambda=lambda.eqlin; end