%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Fuzzy Control System for a Tanker Ship %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % By: Kevin Passino % Version: 4/15/99 % % Notes: This program has evolved over time and uses programming % ideas of Andrew Kwong, Scott Brown, and Brian Klinehoffer. % % This program simulates a fuzzy control system for a tanker % ship. It has a fuzzy controller with two inputs, the error % in the ship heading (e) and the change in that error (c). The output % of the fuzzy controller is the rudder input (delta). We want the % tanker ship heading (psi) to track the reference input heading % (psi_r). We simulate the tanker as a continuous time system % that is controlled by a fuzzy controller that is implemented on % a digital computer with a sampling interval of T. % % This program can be used to illustrate: % - How to code a fuzzy controller (for two inputs and one output, % illustrating some approaches to simplify the computations, for % triangular membership functions, and either center-of-gravity or % center-average defuzzification). % - How to tune the input and output gains of a fuzzy controller. % - How changes in plant conditions ("ballast" and "full") % can affect performance. % - How sensor noise (heading sensor noise), plant disturbances % (wind hitting the side of the ship), and plant operating % conditions (ship speed) can affect performance. % - How improper choice of the scaling gains can result in % oscillations (limit cycles). % - How an improper choice of the scaling gains (or rule base) can % result in an unstable system. % - The shape of the nonlinearity implemented by the fuzzy controller % by plotting the input-output map of the fuzzy controller. % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% clear % Clear all variables in memory % Initialize ship parameters % (can test two conditions, "ballast" or "full"): ell=350; % Length of the ship (in meters) u=5; % Nominal speed (in meters/sec) %u=3; % A lower speed where the ship is more difficult to control abar=1; % Parameters for nonlinearity bbar=1; % The parameters for the tanker under "ballast" conditions % (a heavy ship) are: K_0=5.88; tau_10=-16.91; tau_20=0.45; tau_30=1.43; % The parameters for the tanker under "full" conditions (a ship % that weighs less than one under "ballast" conditions) are: %K_0=0.83; %tau_10=-2.88; %tau_20=0.38; %tau_30=1.07; % Some other parameters are: K=K_0*(u/ell); tau_1=tau_10*(ell/u); tau_2=tau_20*(ell/u); tau_3=tau_30*(ell/u); % Initialize parameters for the fuzzy controller nume=11; % Number of input membership functions for the e % universe of discourse (can change this but must also % change some variables below if you make such a change) numc=11; % Number of input membership functions for the c % universe of discourse (can change this but must also % change some variables below if you make such a change) % Next, we define the scaling gains for tuning membership functions for % universes of discourse for e, change in e (what we call c) and % delta. These are g1, g2, and g0, respectively % These can be tuned to try to improve the performance. % First guess: g1=1/pi;,g2=100;,g0=8*pi/18; % Chosen since: % g1: The heading error is at most 180 deg (pi rad) % g2: Just a guess - that ship heading will change at most % by 0.01 rad/sec (0.57 deg/sec) % g0: Since the rudder is constrained to move between +-80 deg % Tuning: g1=1/pi;,g2=200;,g0=8*pi/18; % Try to reduce the overshoot g1=2/pi;,g2=250;,g0=8*pi/18; % Try to speed up the response a bit but to do this % have to raise g2 a bit to avoid overshoot. Take these % as "good" tuned values. %g1=2/pi;,g2=0.000001;,g0=2000*pi/18; % Values tuned to get an oscillation (limit % cycle) for COG, ballast, % and nominal speed with no sensor % noise or rudder disturbance): % g1: Leave as before % g2: Essentially turn off the derivative gain % since this help induce an oscillation % g0: Make this big to force the limit cycle % In this case simulate for 16,000 sec. %g1=2/pi;,g2=250;,g0=-8*pi/18; % Values tuned to get an instability % g0: Make this negative so that when there % is an error the rudder will drive the % heading in the direction to increase the error % Next, define some parameters for the membership functions we=0.2*(1/g1); % we is half the width of the triangular input membership % function bases (note that if you change g0, the base width % will correspondingly change so that we always end % up with uniformly distributed input membership functions) % Note that if you change nume you will need to adjust the % "0.2" factor if you want membership functions that % overlap in the same way. wc=0.2*(1/g2); % Similar to we but for the c universe of discourse base=0.4*g0; % Base width of output membership fuctions of the fuzzy % controller % Place centers of membership functions of the fuzzy controller: % Centers of input membership functions for the e universe of % discourse of fuzzy controller (a vector of centers) ce=[-1 -0.8 -0.6 -0.4 -0.2 0 0.2 0.4 0.6 0.8 1]*(1/g1); % Centers of input membership functions for the c universe of % discourse of fuzzy controller (a vector of centers) cc=[-1 -0.8 -0.6 -0.4 -0.2 0 0.2 0.4 0.6 0.8 1]*(1/g2); % This next matrix specifies the rules of the fuzzy controller. % The entries are the centers of the output membership functions. % This choice represents just one guess on how to synthesize % the fuzzy controller. Notice the regularity % of the pattern of rules. Notice that it is scaled by g0, the % output scaling factor, since it is a normalized rule base. % The rule base can be tuned to try to improve performance. rules=[1 1 1 1 1 1 0.8 0.6 0.3 0.1 0; 1 1 1 1 1 0.8 0.6 0.3 0.1 0 -0.1; 1 1 1 1 0.8 0.6 0.3 0.1 0 -0.1 -0.3; 1 1 1 0.8 0.6 0.3 0.1 0 -0.1 -0.3 -0.6; 1 1 0.8 0.6 0.3 0.1 0 -0.1 -0.3 -0.6 -0.8; 1 0.8 0.6 0.3 0.1 0 -0.1 -0.3 -0.6 -0.8 -1; 0.8 0.6 0.3 0.1 0 -0.1 -0.3 -0.6 -0.8 -1 -1; 0.6 0.3 0.1 0 -0.1 -0.3 -0.6 -0.8 -1 -1 -1; 0.3 0.1 0 -0.1 -0.3 -0.6 -0.8 -1 -1 -1 -1; 0.1 0 -0.1 -0.3 -0.6 -0.8 -1 -1 -1 -1 -1; 0 -0.1 -0.3 -0.6 -0.8 -1 -1 -1 -1 -1 -1]*g0; % Now, you can proceed to do the simulation or simply view the nonlinear % surface generated by the fuzzy controller. flag1=input('\n Do you want to simulate the \n fuzzy control system \n for the tanker? \n (type 1 for yes and 0 for no) '); if flag1==1, % Next, we initialize the simulation: t=0; % Reset time to zero index=1; % This is time's index (not time, its index). tstop=4000; % Stopping time for the simulation (in seconds) step=1; % Integration step size T=10; % The controller is implemented in discrete time and % this is the sampling time for the controller. % Note that the integration step size and the sampling % time are not the same. In this way we seek to simulate % the continuous time system via the Runge-Kutta method and % the discrete time fuzzy controller as if it were % implemented by a digital computer. Hence, we sample % the plant output every T seconds and at that time % output a new value of the controller output. counter=10; % This counter will be used to count the number of integration % steps that have been taken in the current sampling interval. % Set it to 10 to begin so that it will compute a fuzzy controller % output at the first step. % For our example, when 10 integration steps have been % taken we will then we will sample the ship heading % and the reference heading and compute a new output % for the fuzzy controller. eold=0; % Initialize the past value of the error (for use % in computing the change of the error, c). Notice % that this is somewhat of an arbitrary choice since % there is no last time step. The same problem is % encountered in implementation. x=[0;0;0]; % First, set the state to be a vector x(1)=0; % Set the initial heading to be zero x(2)=0; % Set the initial heading rate to be zero. % We would also like to set x(3) initially but this % must be done after we have computed the output % of the fuzzy controller. In this case, by % choosing the reference trajectory to be % zero at the beginning and the other initial conditions % as they are, and the fuzzy controller as designed, % we will know that the output of the fuzzy controller % will start out at zero so we could have set % x(3)=0 here. To keep things more general, however, % we set the intial condition immediately after % we compute the first controller output in the % loop below. psi_r_old=0; % Initialize the reference trajectory % Next, we start the simulation of the system. This is the main % loop for the simulation of fuzzy control system. while t <= tstop % First, we define the reference input psi_r (desired heading). if t<100, psi_r(index)=0; end % Request heading of 0 deg if t>=100, psi_r(index)=45*(pi/180); end % Request heading of 45 deg if t>2000, psi_r(index)=0; end % Then request heading of 0 deg %if t>4000, psi_r(index)=45*(pi/180); end % Then request heading of 45 deg %if t>6000, psi_r(index)=0; end % Then request heading of 0 deg %if t>8000, psi_r(index)=45*(pi/180); end % Then request heading of 45 deg %if t>10000, psi_r(index)=0; end % Then request heading of 0 deg %if t>12000, psi_r(index)=45*(pi/180); end % Then request heading of 45 deg % Next, suppose that there is sensor noise for the heading sensor with that is % additive, with a uniform distribution on [- 0.01,+0.01] deg. %s(index)=0.01*(pi/180)*(2*rand-1); s(index)=0; % This allows us to remove the noise. psi(index)=x(1)+s(index); % Heading of the ship (possibly with sensor noise). if counter == 10, % When the counter reaches 10 then execute the % fuzzy controller counter=0; % First, reset the counter % Fuzzy controller calculations: % First, for the given fuzzy controller inputs we determine % the extent at which the error membership functions % of the fuzzy controller are on (this is the fuzzification part). c_count=0;,e_count=0; % These are used to count the number of % non-zero mf certainities of e and c e(index)=psi_r(index)-psi(index); % Calculates the error input for the fuzzy controller c(index)=(e(index)-eold)/T; % Sets the value of c eold=e(index); % Save the past value of e for use in the above % computation the next time around the loop % The following if-then structure fills the vector mfe % with the certainty of each membership fucntion of e for the % current input e. We use triangular membership functions. if e(index)<=ce(1) % Takes care of saturation of the left-most % membership function mfe=[1 0 0 0 0 0 0 0 0 0 0]; % i.e., the only one on is the % left-most one e_count=e_count+1;,e_int=1; % One mf on, it is the % left-most one. elseif e(index)>=ce(nume) % Takes care of saturation % of the right-most mf mfe=[0 0 0 0 0 0 0 0 0 0 1]; e_count=e_count+1;,e_int=nume; % One mf on, it is the % right-most one else % In this case the input is on the middle part of the % universe of discourse for e % Next, we are going to cycle through the mfs to % find all that are on for i=1:nume if e(index)<=ce(i) mfe(i)=max([0 1+(e(index)-ce(i))/we]); % In this case the input is to the % left of the center ce(i) and we compute % the value of the mf centered at ce(i) % for this input e if mfe(i)~=0 % If the certainty is not equal to zero then say % that have one mf on by incrementing our count e_count=e_count+1; e_int=i; % This term holds the index last entry % with a non-zero term end else mfe(i)=max([0,1+(ce(i)-e(index))/we]); % In this case the input is to the % right of the center ce(i) if mfe(i)~=0 e_count=e_count+1; e_int=i; % This term holds the index of the % last entry with a non-zero term end end end end % The following if-then structure fills the vector mfc with the % certainty of each membership fucntion of the c % for its current value (to understand this part of the code see the above % similar code for computing mfe). Clearly, it could be more efficient to % make a subroutine that performs these computations for each of % the fuzzy system inputs. if c(index)<=cc(1) % Takes care of saturation of left-most mf mfc=[1 0 0 0 0 0 0 0 0 0 0]; c_count=c_count+1; c_int=1; elseif c(index)>=cc(numc) % Takes care of saturation of the right-most mf mfc=[0 0 0 0 0 0 0 0 0 0 1]; c_count=c_count+1; c_int=numc; else for i=1:numc if c(index)<=cc(i) mfc(i)=max([0,1+(c(index)-cc(i))/wc]); if mfc(i)~=0 c_count=c_count+1; c_int=i; % This term holds last entry % with a non-zero term end else mfc(i)=max([0,1+(cc(i)-c(index))/wc]); if mfc(i)~=0 c_count=c_count+1; c_int=i; % This term holds last entry % with a non-zero term end end end end % The next two loops calculate the crisp output using only the non- % zero premise of error,e, and c. This cuts down computation time % since we will only compute the contribution from the rules that % are on (i.e., a maximum of four rules for our case). The minimum % is used for the premise (and implication for the center-of-gravity % defuzzification case). num=0; den=0; for k=(e_int-e_count+1):e_int % Scan over e indices of mfs that are on for l=(c_int-c_count+1):c_int % Scan over c indices of mfs that are on prem=min([mfe(k) mfc(l)]); % Value of premise membership function % This next calculation of num adds up the numerator for the % center of gravity defuzzification formula. rules(k,l) is the output center % for the rule. base*(prem-(prem)^2/2) is the area of a symmetric % triangle that peaks at one with base width "base" and that is chopped off at % a height of prem (since we use minimum to represent the % implication). Computation of den is similar but without % rules(k,l). num=num+rules(k,l)*base*(prem-(prem)^2/2); den=den+base*(prem-(prem)^2/2); % To do the same computations, but for center-average defuzzification, % use the following lines of code rather than the two above (notice % that in this case we did not use any information about the output % membership function shapes, just their centers; also, note that % the computations are slightly simpler for the center-average defuzzificaton): % num=num+rules(k,l)*prem; % den=den+prem; end end delta(index)=num/den; % Crisp output of fuzzy controller that is the input % to the plant. else % This goes with the "if" statement to check if the counter=10 % so the next lines up to the next "end" statement are executed % whenever counter is not equal to 10 % Now, even though we do not compute the fuzzy controller at each % time instant, we do want to save the data at its inputs and output at % each time instant for the sake of plotting it. Hence, we need to % compute these here (note that we simply hold the values constant): e(index)=e(index-1); c(index)=c(index-1); delta(index)=delta(index-1); end % This is the end statement for the "if counter=10" statement % Now, for the first step, we set the initial condition for the % third state x(3). if t==0, x(3)=-(K*tau_3/(tau_1*tau_2))*delta(index); end % Next, the Runge-Kutta equations are used to find the next state. % Clearly, it would be better to use a Matlab "function" for % F (but here we do not, so we can have only one program). time(index)=t; % First, we define a wind disturbance against the body of the ship % that has the effect of pressing water against the rudder %w(index)=0.5*(pi/180)*sin(2*pi*0.001*t); % This is an additive sine disturbance to % the rudder input. It is of amplitude of % 0.5 deg. and its period is 1000sec. %delta(index)=delta(index)+w(index); % Next, implement the nonlinearity where the rudder angle is saturated % at +-80 degrees if delta(index) >= 80*(pi/180), delta(index)=80*(pi/180); end if delta(index) <= -80*(pi/180), delta(index)=-80*(pi/180); end % Next, we use the formulas to implement the Runge-Kutta method % (note that here only an approximation to the method is implemented where % we do not compute the function at multiple points in the integration step size). F=[ x(2) ; x(3)+ (K*tau_3/(tau_1*tau_2))*delta(index) ; -((1/tau_1)+(1/tau_2))*(x(3)+ (K*tau_3/(tau_1*tau_2))*delta(index))-... (1/(tau_1*tau_2))*(abar*x(2)^3 + bbar*x(2)) + (K/(tau_1*tau_2))*delta(index) ]; k1=step*F; xnew=x+k1/2; F=[ xnew(2) ; xnew(3)+ (K*tau_3/(tau_1*tau_2))*delta(index) ; -((1/tau_1)+(1/tau_2))*(xnew(3)+ (K*tau_3/(tau_1*tau_2))*delta(index))-... (1/(tau_1*tau_2))*(abar*xnew(2)^3 + bbar*xnew(2)) + (K/(tau_1*tau_2))*delta(index) ]; k2=step*F; xnew=x+k2/2; F=[ xnew(2) ; xnew(3)+ (K*tau_3/(tau_1*tau_2))*delta(index) ; -((1/tau_1)+(1/tau_2))*(xnew(3)+ (K*tau_3/(tau_1*tau_2))*delta(index))-... (1/(tau_1*tau_2))*(abar*xnew(2)^3 + bbar*xnew(2)) + (K/(tau_1*tau_2))*delta(index) ]; k3=step*F; xnew=x+k3; F=[ xnew(2) ; xnew(3)+ (K*tau_3/(tau_1*tau_2))*delta(index) ; -((1/tau_1)+(1/tau_2))*(xnew(3)+ (K*tau_3/(tau_1*tau_2))*delta(index))-... (1/(tau_1*tau_2))*(abar*xnew(2)^3 + bbar*xnew(2)) + (K/(tau_1*tau_2))*delta(index) ]; k4=step*F; x=x+(1/6)*(k1+2*k2+2*k3+k4); % Calculated next state t=t+step; % Increments time index=index+1; % Increments the indexing term so that % index=1 corresponds to time t=0. counter=counter+1; % Indicates that we computed one more integration step end % This end statement goes with the first "while" statement % in the program so when this is complete the simulation is done. % % Next, we provide plots of the input and output of the ship % along with the reference heading that we want to track. % Also, we plot the two inputs to the fuzzy controller. % % First, we convert from rad. to degrees psi_r=psi_r*(180/pi); psi=psi*(180/pi); delta=delta*(180/pi); e=e*(180/pi); c=c*(180/pi); % Next, we provide plots of data from the simulation figure(1) clf subplot(211) plot(time,psi,'k-',time,psi_r,'k--') grid on xlabel('Time (sec)') title('Ship heading (solid) and desired ship heading (dashed), deg.') subplot(212) plot(time,delta,'k-') grid on xlabel('Time (sec)') title('Rudder angle (\delta), deg.') zoom figure(2) clf subplot(211) plot(time,e,'k-') grid on xlabel('Time (sec)') title('Ship heading error between ship heading and desired heading, deg.') subplot(212) plot(time,c,'k-') grid on xlabel('Time (sec)') title('Change in ship heading error, deg./sec') zoom end % This ends the if statement (on flag1) on whether you want to do a simulation % or just see the control surface %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Next, provide a plot of the fuzzy controller surface: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Request input from the user to see if they want to see the % controller mapping: flag2=input('\n Do you want to see the nonlinear \n mapping implemented by the fuzzy \n controller? \n (type 1 for yes and 0 for no) '); if flag2==1, % First, compute vectors with points over the whole range of % the fuzzy controller inputs plus 20% over the end of the range % and put 100 points in each vector e_input=(-(1/g1)-0.2*(1/g1)):(1/100)*(((1/g1)+0.2*(1/g1))-(-(1/g1)-... 0.2*(1/g1))):((1/g1)+0.2*(1/g1)); ce_input=(-(1/g2)-0.2*(1/g2)):(1/100)*(((1/g2)+0.2*(1/g2))-(-(1/g2)-... 0.2*(1/g2))):((1/g2)+0.2*(1/g2)); % Next, compute the fuzzy controller output for all these inputs for jj=1:length(e_input) for ii=1:length(ce_input) c_count=0;,e_count=0; % These are used to count the number of % non-zero mf certainities of e and c % The following if-then structure fills the vector mfe % with the certainty of each membership fucntion of e for the % current input e. We use triangular membership functions. if e_input(jj)<=ce(1) % Takes care of saturation of the left-most % membership function mfe=[1 0 0 0 0 0 0 0 0 0 0]; % i.e., the only one on is the % left-most one e_count=e_count+1;,e_int=1; % One mf on, it is the % left-most one. elseif e_input(jj)>=ce(nume) % Takes care of saturation % of the right-most mf mfe=[0 0 0 0 0 0 0 0 0 0 1]; e_count=e_count+1;,e_int=nume; % One mf on, it is the % right-most one else % In this case the input is on the middle part of the % universe of discourse for e % Next, we are going to cycle through the mfs to % find all that are on for i=1:nume if e_input(jj)<=ce(i) mfe(i)=max([0 1+(e_input(jj)-ce(i))/we]); % In this case the input is to the % left of the center ce(i) and we compute % the value of the mf centered at ce(i) % for this input e if mfe(i)~=0 % If the certainty is not equal to zero then say % that have one mf on by incrementing our count e_count=e_count+1; e_int=i; % This term holds the index last entry % with a non-zero term end else mfe(i)=max([0,1+(ce(i)-e_input(jj))/we]); % In this case the input is to the % right of the center ce(i) if mfe(i)~=0 e_count=e_count+1; e_int=i; % This term holds the index of the % last entry with a non-zero term end end end end % The following if-then structure fills the vector mfc with the % certainty of each membership fucntion of the c % for its current value. if ce_input(ii)<=cc(1) % Takes care of saturation of left-most mf mfc=[1 0 0 0 0 0 0 0 0 0 0]; c_count=c_count+1; c_int=1; elseif ce_input(ii)>=cc(numc) % Takes care of saturation of the right-most mf mfc=[0 0 0 0 0 0 0 0 0 0 1]; c_count=c_count+1; c_int=numc; else for i=1:numc if ce_input(ii)<=cc(i) mfc(i)=max([0,1+(ce_input(ii)-cc(i))/wc]); if mfc(i)~=0 c_count=c_count+1; c_int=i; % This term holds last entry % with a non-zero term end else mfc(i)=max([0,1+(cc(i)-ce_input(ii))/wc]); if mfc(i)~=0 c_count=c_count+1; c_int=i; % This term holds last entry % with a non-zero term end end end end % The next loops calculate the crisp output using only the non- % zero premise of error,e, and c. num=0; den=0; for k=(e_int-e_count+1):e_int % Scan over e indices of mfs that are on for l=(c_int-c_count+1):c_int % Scan over c indices of mfs that are on prem=min([mfe(k) mfc(l)]); % Value of premise membership function % This next calculation of num adds up the numerator for the % center of gravity defuzzification formula. num=num+rules(k,l)*base*(prem-(prem)^2/2); den=den+base*(prem-(prem)^2/2); % To do the same computations, but for center-average defuzzification, % use the following lines of code rather than the two above: % num=num+rules(k,l)*prem; % den=den+prem; end end delta_output(ii,jj)=num/den; % Crisp output of fuzzy controller that is the input % to the plant. end end % Convert from radians to degrees: delta_output=delta_output*(180/pi); e_input=e_input*(180/pi); ce_input=ce_input*(180/pi); % Plot the controller map figure(3) clf surf(e_input,ce_input,delta_output); view(145,30); colormap(white); xlabel('Heading error (e), deg.'); ylabel('Change in heading error (c), deg.'); zlabel('Fuzzy controller output (\delta), deg.'); title('Fuzzy controller mapping between inputs and output'); end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % End of program % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%