PI Controller - Integral term is causing instability

107 Views Asked by At

I am simulating the height of the reservoir in a hydro power plant using Matlab, without Simulink. The radial gates of the power plant are used to control the height of the water in the reservoir. This is done by implementing a PI controller on the radial gates. First, I am trying to let things work with only one radial gate.


The flow into the reservoir is chosen to be $$Q_{in} = 400 \,\,m^3/s. \qquad \qquad (1)$$ The output flow through a radial gate is given by $$Q_{out} = \frac{dV}{dt} = C_g(D) \cdot D \cdot L \cdot \sqrt{2\cdot g \cdot H_g} \qquad \qquad (2)$$ where $$H_g = H - \frac{D}{2} \qquad \qquad (3)$$. enter image description here

Rewriting eq. (2) with respect to (3) gives $$Q_{out} = C_g(D) \cdot D \cdot L \cdot \sqrt{2g (H - \frac{D}{2})} \\ = C_g(D) \cdot D \cdot L \cdot \sqrt{g} \cdot \sqrt{2H-D}. \qquad \qquad (4)$$ Grouping all the constants as $C_1 = L \cdot \sqrt{g}$ gives

$$Q_{out} = C_g(D) \cdot D \cdot C_1 \cdot \sqrt{2H-D} \qquad \qquad (5)$$

Eq. (5) includes following variables and constants:

  • $C_g(D)$ is a function dependent on the gate opening $D$.
  • $D$ is the gate opening. It's interval is 0 - 8.7 meters.
  • $C_1$ consists of $L$ which is the width of the gate and gravity $g$.
  • $H$ is the height of the reservoir, calculated from the edge of the lower part of the gate where $D = 0$.

Furthermore, I am using a PI controller to control the height $H$. I am using a for-loop in Matlab to simulate this problem. The discrete PI controller I use is calculated as follows.

The error in Matlab is calculated by $$error(1,i) = H(1,i) - H_d(1,i)$$ where $H$ is the height and $H_d$ is the reference value or the set point. The height of the reservoir should always be at $116 m$ above sea level. In the program, I subtract the lower edge of the radial gate, where $D = 0m$, which is $H_{edge} = 110m$, so the radial gate is placed very high above sea level. I also choose the initial height in the simulation to be $H_{initial} = 115.8 m$, therefore, $$H(1,1) = H_{initial} - H_{edge} = 115.8 - 110 = 5.8m \\ H_d(1,1) = H_{d} - H_{edge} = 116 - 110 = 6m.$$ This gives an initial error of $error = -0.2m$.

Next thing I do is to calculate the $P$ and the $I$ terms: $$PI\_P(1,i) = k_p \cdot error(1,i) \\ PI\_I(1,i) = (\frac{k_i}{T_i}) \cdot (PI\_I(1,i-1) + 0.5 \cdot (error(1,i) + error(1,i-1))\cdot dt) - intWindup(1,i)$$

The integral term is actually calculated in the program as

    if i == 1
        PI_I(1,i) = ((ki/Ti)*error(1,i)*dt) - intWindup(1,i);
    else
        PI_I(1,i) = ((ki/Ti)*(PI_I(1,i-1) + 0.5*(error(1,i) + error(1,i-1))*dt)) - intWindup(1,i);
    end

The controller output is then calculated as the sum of the two terms: $$ PI\_u(1,i) = PI\_P(1,i) + PI\_I(1,i) $$


Since the radial gate can only vary between 0m up to 8.7m, I used a integral windup method called "backwards calculation" so that the integrator will not "windup". This is shown in the image below:

enter image description here

Next thing I do is calculate the saturation $$u\_sat(1,i) = CheckSaturation(PI\_u(1,i),Dmax,Dmin)$$ The function I wrote is given below

function u_sat = CheckSaturation(u,gateMax,gateMin)
% Controller saturation check

    if u >= gateMax
        u_sat = gateMax;
    elseif u <= gateMin
        u_sat = gateMin;
    else
        u_sat = u;
    end
end

After this, I calculate the integral windup as $$intWindup(1,i+1) = intWindupBackCalculation(PI\_u(1,i),u\_sat(1,i),k\_lim)$$ where $k\_lim = 1$. The function I wrote is given by

function val = intWindupBackCalculation(u,u_sat,k_lim)
    diff = u - u_sat;
    val = diff * k_lim;
end

Now finally, I update the model of the reservoir with the following code

    Cg1(1,i)     = Cg_D(D1(1,i),Dmin,Dmax);


    % The physical system
    Qout_gate1(1,i)  = Cg1(1,i) * C1 * D1(1,i) * sqrt(2*H(1,i) - D1(1,i));
    Qout(1,i)        = Qout_gate1(1,i) + Qout_m;
    H_dot(1,i)      = (1/A(1,i)) * (Qin(1,i) - Qout(1,i));
    H(1,i+1)        = H(1,i) + 0.5 * (H_dot(1,i) + H_dot(1,i+1)) * dt;

This is simply calculation of eq. (2) from above, $Qout\_m = 0$ is the flow through the machines which is excluded in the calculations. The differential equation used to calculate the change of height is $$ \dot H = \frac{1}{A} (Q_{in} - Q_{out}) $$ then in the last line of the code, I use discrete integral to calculate the height.


My question starts now.

Below is a plot where I choose $k_p = 40$ and $k_i = 0.1$

enter image description here

enter image description here

  • $H$ is the change of height per second
  • $H_d$ is the desired height, or the reference level, 116m
  • $D1$ is the radial gate opening in meters
  • error is simply the error, the blue line, while the integral windup is the red line. it is also possible to see the proportional and integral gains in the legend.
  • Qin is the red line and Qout is the blue line
  • $PI\_P$ is the proportional term of the controller
  • $PI\_I$ is the integral term of the controller

In these figures above, it can be seen that the controller tries to keep the water height at 116m but there is this settling error, I think it's related to slowness of the integral term. It can also be seen that the radial gates opens up and it tries to regulate the water heigt. The error can be seen as very small related to the integral windup, but the error is there and it's not always zero. The flow in vs. flow out starts to be equal slowly. On the last figure, it can be clearly seen that the proportional term and the integral term is working.


Now I choose $k_p = 40$ again, but the integral gain $k_i = 1$

enter image description here

enter image description here

In the figures above, the settling error is much smaller now and the response is much faster. The integrator is doing much more now, but not enough. I want the water level to be at 116m above sea level with only +/- 5cm. In real life, this system is very slow. The reservoir is huge, its area is $A = 3.8 \cdot 1000^2 \, \, m^2$ or $3.8 \,\, km^2$.


Now I want to increase the integral gain, and go above 1. So I choose $k_p = 40$ and $k_i = 2$.

enter image description here

enter image description here

In the figures above, it can clearly be seen that everything is very slow now which is not acceptable.


Now I increase the integral term to $k_i = 5$ and everything goes unstable and crashes as you can see in the figures below.

enter image description here

enter image description here


My question is: What can I do about this problem? Do you have any idea of what I'm doing wrong or what I could do better? The integrator is being a little bit of problem for me and I thing it's a bit strange that the system crashes.


The whole code is below:

t0      = 0.0;           % [s] Initial time
days    = 0;
hours   = 8;
minutes = 60;
seconds = 60;

F_freq = 50;    % Frequency (increase -> smaller dt, decrease greater dt)
Fs = 2*F_freq;   % Samples per second (Sample frequency Fs must be at least two times the frequency F_freq to avoid aliasing)
dt = 1/Fs;       % Seconds per sample

if days == 0 && hours ~= 0 && minutes ~= 0 && seconds ~= 0
    t_f     = seconds*minutes*hours; % [s] Final time
elseif days == 0 && hours == 0 && minutes ~= 0 && seconds ~= 0
    t_f     = seconds*minutes; % [s] Final time
elseif days == 0 && hours == 0 && minutes == 0 && seconds ~= 0
    t_f     = seconds; % [s] Final time
else
    t_f     = seconds*minutes*hours*days; % [s] Final time
end

t       = (t0:dt:t_f-dt)';    % [s] Sample instants
T_plot  = t';

Nsim    = length(t);

% A           = 3.8 * 1000^2; % [m^2]     Area of Hagalón, "equivalent to 3.8 km^2"
% Qout_machine  = 352;      % [m^3/s]   Max flow through machines.
% Qin(1,1:Nsim)         = [200*ones(1,round(Nsim/8)) 300*ones(1,round(Nsim/8)) 400*ones(1,round(Nsim/8)) 500*ones(1,round(Nsim/8))...
%                          600*ones(1,round(Nsim/8)) 700*ones(1,round(Nsim/8)) 600*ones(1,round(Nsim/8)) 500*ones(1,round(Nsim/8))];          % [m^3/s]   Flow into Hagalón

Qin(1,1:Nsim)         = 400;
  
H_edge      = 110;
H_initial   = 115.8 - H_edge;        % [m]       Initial height of the lagoon
% Hd          = [116*ones(1,round(Nsim/2)) 116.4*ones(1,round(Nsim/2))] - H_edge;                  
% [m]       H desired (reference value) 
Hd(1,1:Nsim)          = 116 - H_edge;          % [m]       H desired (reference value) 

% Parameters for radial gate
L       = 12;           % [m]
g_sqrt  = sqrt(9.82);   % [m/s^2]
C1      = L*g_sqrt;     % Group C1 as a constant [m^2/s^2]

Dmax    = 8.7;          % [m] Maximum opening D of a radial gate
% Dmax    = 15;         % [m] Maximum opening D of a radial gate
Dmin    = 0;            % [m] Minimum opening D of a radial gate

a_slope = 8.7/522;

% Model
H_dot(1,1:Nsim)     = 0;
H(1,1:Nsim)         = H_initial;
D1(1,1:Nsim)        = 0;
Cg1(1,1:Nsim)       = Cg_D(D1(1,1),Dmin,Dmax);
A(1,1:Nsim)         = 3.8 * 1000^2;
Qout_m              = 0; % Flow out of two machines. Max flow is Qout_m = 352 m^3/s
Qout_gate1(1,1:Nsim)= 0;
Qout_gate1(1,1)= Cg1(1,1) * C1 * D1(1,1) * sqrt(2*H(1,1) - D1(1,1));
Qout(1,1:Nsim)      = 0;

% PI
error(1,1:Nsim)     = H(1,1) - Hd(1,1);
PI_P(1,1:Nsim)      = 0;
% PI_I(1,1:Nsim)      = 0.000518663;
PI_I(1,1:Nsim)      = 0;
PI_u(1,1:Nsim)      = 0;
u_sat(1,1:Nsim)     = 0;

% P controller for the radial gate opening
error_gate(1,1:Nsim)    = 0;
PI_P_gate(1,1:Nsim)     = 0;
PI_I_gate(1,1:Nsim)     = 0;
PI_u_gate(1,1:Nsim)     = 0;
u_sat_gate(1,1:Nsim)    = 0;

% P and I gain coefficentes for the height of the reservoir
kp          = 40;               % Proportional Gain
% tau_i       = seconds*minutes;  % Time constant for I term
% ki          = 1/46104;
ki          = 5;
k_lim       = 1;
% ki          = 1/tau_i;          % Integral Gain
% ki=0;
% ki=kp;
% Ti          = 1/dt;             % Time constant for integral term
Ti          = 1;

% P gain coefficient for the gate opening
kp_gate     = 1;

% Anti-Reset Windup switch coefficient
kc(1,1:Nsim)        = 1;
intWindup(1,1:Nsim) = 0;
kc_gate(1,1:Nsim)   = 1;

for i = 1 : Nsim-1


% PI controller for the height in the reservoir

error(1,i) = H(1,i) - Hd(1,i); % Correct one

PI_P(1,i)   = kp*error(1,i);
if i == 1
    PI_I(1,i) = ((ki/Ti)*error(1,i)*dt) - intWindup(1,i);
else
    PI_I(1,i) = ((ki/Ti)*(PI_I(1,i-1) + 0.5*(error(1,i) + error(1,i-1))*dt)) - intWindup(1,i);
end
PI_u(1,i)   = PI_P(1,i) + PI_I(1,i);

% Check saturation of the radial gate
u_sat(1,i)  = CheckSaturation(PI_u(1,i),Dmax,Dmin);

% Calculage anti-reset windup calculation using clamping
%     kc(1,i+1)     = intWindupClamping(error(1,i+1),PI_u(1,i),u_sat(1,i));
  intWindup(1,i+1) = intWindupBackCalculation(PI_u(1,i),u_sat(1,i),k_lim);

% Feedback for the opening of the radial gate (slowness)
if i == 1
    error_gate(1,i) = 0;
    D1(1,i) = kp_gate*a_slope*error_gate(1,i);
else
    error_gate(1,i) = u_sat(1,i) - D1(1,i-1);
    D1(1,i) = D1(1,i-1) + kp_gate*a_slope*error_gate(1,i);
end
%     D1(1,i) = u_sat(1,i);

Cg1(1,i)     = Cg_D(D1(1,i),Dmin,Dmax);
%     Cg1(1,i)     = 0.7;

% The physical system
Qout_gate1(1,i)  = Cg1(1,i) * C1 * D1(1,i) * sqrt(2*H(1,i) - D1(1,i));
Qout(1,i)        = Qout_gate1(1,i) + Qout_m;
H_dot(1,i)      = (1/A(1,i)) * (Qin(1,i) - Qout(1,i));
H(1,i+1)        = H(1,i) + 0.5 * (H_dot(1,i) + H_dot(1,i+1)) * dt;

%     A(1,i) = AreaReservoir(H(1,i+1));

end

PI_P(1,i+1)   = kp*error(1,i+1);
Qout_gate1(1,i+1)  = Cg1(1,i) * C1 * D1(1,i+1) * sqrt(2*H(1,i) - D1(1,i+1));

figure; % Change of height
%sgtitle('Change of height per sec - Tank')
sgtitle('Change of height per sec - Tank')
subplot(4,1,1)
plot(T_plot,H + H_edge,"LineWidth",2); hold on;
plot(T_plot,Hd + H_edge,"--","LineWidth",2);
legend("H","Hd - reference");
ylabel('H [m]'); xlabel('Time [s]')
xlim([t0 t_f]); grid;
% ylim([0 120]); grid;
subplot(4,1,2)
plot(T_plot,D1,"LineWidth",2); hold on;
legend('D1')
ylabel('D1 [m]'); xlabel('Time [s]')
xlim([t0 t_f]); grid;
subplot(4,1,3)
plot(T_plot,error,"LineWidth",2); hold on;
% plot(T_plot,kc,"LineWidth",2);
plot(T_plot,intWindup,"LineWidth",2);
ylabel('error [m]'); xlabel('Time [s]')
xlim([t0 t_f]); grid;
comment_1 = sprintf("error - kp = %.6f",kp);
comment_2 = sprintf("Windup - ki = %.6f",ki);
legend(comment_1,comment_2)
subplot(4,1,4)
plot(T_plot,Qout,"LineWidth",2); hold on;
plot(T_plot,Qin,"LineWidth",2);
ylabel('Q [m]'); xlabel('Time [s] (2 dagar)')
xlim([t0 t_f]); ylim([0 (max(Qin)+100)]); grid;
legend("Qout [m^3/s] & D = 5m", "Qin [m^3/s] & D = 5m")
% title("Qin & Qout vs. time")

figure; % Change of height
%sgtitle('Change of height per sec - Tank')
sgtitle('PI controller - Tank')
subplot(2,1,1)
plot(T_plot,PI_P,"LineWidth",2); 
ylabel('PI_P [m]'); xlabel('Time [s]')
xlim([t0 t_f]); grid;
subplot(2,1,2)
plot(T_plot,PI_I,"LineWidth",2); 
ylabel('PI_I [m]'); xlabel('Time [s]')
xlim([t0 t_f]); grid;

function Cg_vs_D = Cg_D(D,Dmin,Dmax) 
% Calculation of Cg variable of a ratial gate

n = 7; % Degree of the polynomial, got it from the interpolation process for Cg vs. D

if D < Dmin
    Cg_vs_D = 0.7743;
elseif D > Dmax
    Cg_vs_D = 0.6513;
else
    Cg_vs_D =   -5.1854385647666910937633559519621684330559219233692e-07*D^n ...
            + 2.3009388243133869431924706794312385227385675534606e-05*D^(n-1) ...
            - 0.00039326051774348001841691280233703764679376035928726*D^(n-2) ...
            + 0.0032245124407480371502010552120509601081721484661102*D^(n-3) ...
            - 0.012403237757656533982175695030036877142265439033508*D^(n-4) ...
            + 0.01661740981151977464280733443047211039811372756958*D^(n-5) ...
            - 0.012264797555097968484449921788836945779621601104736*D^(n-6) ...
            + 0.77434799435072565465532079542754217982292175292969;
end
end

function u_sat = CheckSaturation(u,gateMax,gateMin)
% Controller saturation check

if u >= gateMax
    u_sat = gateMax;
elseif u <= gateMin
    u_sat = gateMin;
else
    u_sat = u;
end
end

function val = intWindupBackCalculation(u,u_sat,k_lim)

diff = u - u_sat;
val = diff * k_lim;

end