function varargout = RLocusGui(varargin)
% RLocusGui M-file for RLocusGui.fig
%
% Proper syntax for calling the function is RLocusGui(sys), where "sys"
% is a transfer function object (this requires the control-systems
% toolbox.)
%
% The function take the transfer function and and plots the root locus
% (using Matlab's "rlocus" command. It then describes and illustrates
% all of the "textbook" rules for plotting the root locus. It also creates
% a web page that demonstrates all of the rules.
%
% It was created using "GUIDE," Matlab's GUI creation tool.
%
%Written by Erik Cheever (Copyright 2007)
%Contact: erik_cheever@swarthmore.edu
% Erik Cheever
% Dept. of Engineering
% Swarthmore College
% 500 College Avenue
% Swarthmore, PA 19081 USA
% Last Modified by GUIDE v2.5 12-Feb-2007 12:43:57
% Begin initialization code - DO NOT EDIT
gui_Singleton = 1;
gui_State = struct('gui_Name', mfilename, ...
'gui_Singleton', gui_Singleton, ...
'gui_OpeningFcn', @RLocusGui_OpeningFcn, ...
'gui_OutputFcn', @RLocusGui_OutputFcn, ...
'gui_LayoutFcn', [] , ...
'gui_Callback', []);
if nargin && ischar(varargin{1})
gui_State.gui_Callback = str2func(varargin{1});
end
if nargout
[varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
gui_mainfcn(gui_State, varargin{:});
end
% End initialization code - DO NOT EDIT
%% The code below is split into several "cells." (See MatLab docs).------
% The next cell contains all of the code that Matlab's GUIDE created that
% was not modified, and is empty.
%
% This is followed by initialization code.
%
% This is followed by utility functions that are called by several
% other functions. This section also includes code that runs GUI.
%
% This is followed by the code that generates the web page html.
%
% The last cell includes all of the code that describes the "Root Locus
% Rules"
%-------------------------------------------------------------------------
%% Empty Matlab code (no comments) ---------------------------------------
% --- Outputs from this function are returned to the command line.
function varargout = RLocusGui_OutputFcn(hObject, eventdata, handles)
%Empty function (no output arguments)
% --- Executes on selection change in lbRuleDescr.
function lbRuleDescr_Callback(hObject, eventdata, handles)
% --- Executes during object creation, after setting all properties.
function lbRuleDescr_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'),...
get(0,'defaultUicontrolBackgroundColor'))
set(hObject,'BackgroundColor','white');
end
% --- Executes during object creation, after setting all properties.
function sldKIndex_CreateFcn(hObject, eventdata, handles)
if isequal(get(hObject,'BackgroundColor'), ...
get(0,'defaultUicontrolBackgroundColor'))
set(hObject,'BackgroundColor',[.9 .9 .9]);
end
%-------------------------------------------------------------------------
%% Initialization code
% --- Executes just before RLocusGui is made visible. --------------------
% This function makes sure inputs are appropriate, and
% also determines fundamental information about transfer
% function (order of numerator and denominator polynomials...).
function RLocusGui_OpeningFcn(hObject, eventdata, handles, varargin)
%The first time the code is called, it is slow to start up, so display
% a "waitbar" dialog.
hWait=waitbar(0.1,'Please wait while GUI initializes.');
nargin=length(varargin);
%Make sure we have one argument, and that it is a 'tf'
if (nargin~=1) || (~isa(varargin{1},'tf'))
disp(' ');
disp('Root Locus Plotter - proper usage is ''RLocusGui(Sys)'',')
disp(' where ''Sys'' is a transfer function object.');
disp(' '); disp(' ');
close(handles.RLocusGuiFig);
close(hWait);
return
end
disp(' '); disp(' ');
Sys=minreal(varargin{1}); %Get minimum realization.
% Get numerator and denominator of two realizations. If their
% lengths are unequal, it means that there were poles and zeros that
% cancelled.
[n1,d1]=tfdata(Sys,'v');
[n2,d2]=tfdata(varargin{1},'v');
if (length(n1)~=length(n2)),
disp('***************************Warning*****************************');
disp('Original transfer function was:');
varargin{1}
disp('Some poles and zeros were equal. After cancellation:');
Sys
disp('The simplified transfer function is the one that will be used.');
disp('**************************************************************');
disp(' ');
beep;
s{1}='System has poles and zeros that cancel.';
s{2}='See command window for details.';
waitfor(warndlg(s));
end
handles.Sys=Sys; %The variable Sys is the transfer function.
% Choose default command line output for RLocusGui.
% This was generated by Matlab, but is never used
handles.output = hObject;
handles.ColorOrder=get(gca,'ColorOrder'); %Save color order;
handles.HighlightColor=[1 0.75 0.75]; %Color for highlights (pink).
waitbar(0.25);
guidata(hObject, handles); % Save changes to handle.
getSysInfo(hObject, handles); % Get useful information about Xfer func.
handles=guidata(hObject); % Reload handles (changed in getTFInfor)
waitbar(0.5);
InitRlocus(handles);
waitbar(0.75);
set(handles.rbInfo,'Value',1); %Choose first radio button.
%Display it as the "SelectedObject"
set(handles.panelChooseRule,'SelectedObject',handles.rbInfo)
set(handles.lbRuleDescr,'String',RuleInfo(handles));
set(handles.axRules,'Visible','off'); %Hide second plot.
set(handles.txtKval,'Visible','off'); %Hide text on plot.
set(handles.sldKIndex,'visible','off');%Hide slider.
set(handles.txtKeq0,'visible','off'); %Hide "K=0" text for slider.
set(handles.txtKeqInf,'visible','off');%Hide "K=Inf" text for slider.
set(handles.cbInteract,'visible','off');%Hide Checkbox.
% This next variable is a kludge. For the last two "rules," the user can
% interact with the GUI. This is distinct from the others, and I needed
% a way to keep track of this. If interaction is ongoing, the variable is
% set to 1. Normally it will be 0.
handles.interactive=0;
handles.kInd=0; %Index into array of gain (K) values.
RLocusDispTF(handles); % Disp Xfer function
guidata(hObject, handles); % Update handles structure
waitbar(1.0);
close(hWait);
% ------------------------------------------------------------------------
% ------------------------------------------------------------------------
function RLocusDispTF(handles)
% This function displays a tranfer function that is a helper function.
% It takes the transfer function of the and splits it
% into three lines so that it can be displayed nicely. For example:
% " s + 1"
% "H(s) = ---------------"
% " s^2 + 2 s + 1"
% The numerator string is in the variable nStr,
% the second line is in divStr,
% and the denominator string is in dStr.
% Get numerator and denominator.
[n,d]=tfdata(handles.Sys,'v');
% Get string representations of numerator and denominator
nStr=poly2str(n,'s'); dStr=poly2str(d,'s');
% Find length of strings.
LnStr=length(nStr); LdStr=length(dStr);
if LnStr>LdStr,
%the numerator is longer than denominator string, so pad denominator.
n=LnStr; %n is the length of the longer string.
nStr=[' ' nStr]; %add spaces for characters at start of divStr.
dStr=[' ' blanks(floor((LnStr-LdStr)/n)) dStr]; %pad denominator.
else
%the demoninator is longer than numerator, pad numerator.
n=LdStr;
nStr=[' ' blanks(floor((LdStr-LnStr)/n)) nStr];
dStr=[' ' dStr];
end
divStr='G(s)H(s)= ';
for i=1:n, divStr=[divStr '-']; end
set(handles.txtXfer,'String',{nStr,divStr,dStr});
%Change type font and size.
set(handles.txtXfer,'FontName','Courier New')
set(handles.txtXfer,'FontSize',8)
guidata(handles.RLocusGuiFig, handles); %save changes to handles.
% ------------------End of function RLocusDispTF -------------------------
% ------This function gets all of the information from transfer function.-
function getSysInfo(hObject, handles)
sys=handles.Sys;
[num,den]=tfdata(sys,'v'); %Get (and save) numerator and denominator.
handles.Num=num; handles.Den=den;
[z,p]=zpkdata(sys,'v'); %Get zeros and poles (to accuracy of 0.01)
z=round(real(z)*100)/100+j*round(imag(z)*100)/100;
p=round(real(p)*100)/100+j*round(imag(p)*100)/100;
realZIndex=find(abs(imag(z))<1E-3); %Determine which are real (i.e., on
realPIndex=find(abs(imag(p))<1E-3); % the real axis)...
z(realZIndex)=real(z(realZIndex)); % and set imag part to zerp.
p(realPIndex)=real(p(realPIndex));
handles.Z=z; handles.P=p; %Store zeros and poles.
m=length(z); n=length(p); %Length of numerator and denominator.
q=n-m; %Number of zeros at infinity.
handles.M=m; handles.N=n; handles.Q=q; %Store values.
[r,k1]=rlocus(sys); % Let Matlab calculate appropriate range for k
for i=1:(length(k1)-1), %Generate intermediate points (smoother plots)
k(2*(i-1)+1)=k1(i); %Take value of k, but also...
k(2*i)=(k1(i)+k1(i+1))/2; %generate new point between consecutive k's
end
[r,k]=rlocus(sys,k); %Recalculate with finer sampling of k.
handles.R=r; handles.K=k; %Save.
% Slider for "k" has stops at each value of "k."
set(handles.sldKIndex,'Max',length(k));
set(handles.sldKIndex,'Min',1);
set(handles.sldKIndex,'SliderStep',[1/length(k) 2/length(k)]);
set(handles.sldKIndex,'Value',1);
% Get information for autoscaling. This is largely a kludge. Determine
% the min and max value of the axes as a multiple ("scale") of the min and
% max values of the zeros and poles of the transfer function.
scale=1.5;
if ~isempty(z),
rlPzMin=min(min(real(p)),min(real(z)))*scale;
rlPzMax=max(max(real(p)),max(real(z)))*scale;
imPzMax=max(max(imag(p)),max(imag(z)))*scale;
else %special case if there are no zeros.
rlPzMin=min(real(p))*scale;
rlPzMax=max(real(p))*scale;
imPzMax=max(imag(p))*scale;
end
if q~=0, %if the locus goes to infinity, make plot "scale" times bigger.
xmax=ceil(scale*rlPzMax); xmin=floor(scale*rlPzMin);
else % just a little bigger.
xmax=ceil(rlPzMax+0.1); xmin=floor(rlPzMin-0.1);
end
handles.Xmax=max(xmax,1); %Max is at least 1.
handles.Xmin=min(-1,xmin); %Min is less than -1.
handles.Ymax=max([ceil(1.5*imPzMax) 0.75*abs([xmax xmin]) 1]);
% find all complex poles and zeros (for angles of departure...)
handles.cmplxZero=[]; handles.cmplxPole=[];
for i=1:length(z),
if imag(z(i))>0, handles.cmplxZero(end+1)=z(i); end
end
for i=1:length(p),
if imag(p(i))>0, handles.cmplxPole(end+1)=p(i); end
end
guidata(hObject, handles); %save changes to handles.
%-------------------------------------------------------------------------
%------Draw locus on left-hand set of axes (unchanged thereafter)----------
function InitRlocus(handles)
axes(handles.axStatic); cla;
drawRLocus(handles,handles.axStatic,1.5,length(handles.K),...
'Complete Root Locus');
%--------------------------------------------------------------------------
%% Utility functions and GUI control
% --- Executes on button press in pbExit. Closes Gui ---------------------
function pbExit_Callback(hObject, eventdata, handles)
disp(' '); disp('Root Locus GUI tool closed.'); disp(' '); disp(' ');
close(handles.RLocusGuiFig);
%--------------------------------------------------------------------------
% --- Executes on button press in pbWebPage. Goes to my web page.----------
function pbWebPage_Callback(hObject, eventdata, handles)
web('http://www.swarthmore.edu/NatSci/echeeve1/Ref/LPSA/Root_Locus/DeriveRootLocusRules.html');
%--------------------------------------------------------------------------
% --- Draw grid on left set of axes if check box is checked ---------------
function cbRLocGrid_Callback(hObject, eventdata, handles)
if get(handles.cbRLocGrid,'Value'),
axes(handles.axStatic);
sgrid(0.1:0.2:0.9,1:1:2*sqrt((handles.Ymax)^2));
else
InitRlocus(handles);
end
get(handles.cbRLocGrid,'Value');
%--------------------------------------------------------------------------
% --Called when radio button is pushed, dispatches the correct function.---
function panelChooseRule_SelectionChangeFcn(hObject, eventdata, handles)
s=''; %Start "s" as an empty string.
set(handles.axRules,'Visible','on'); %Second axes visible.
set(handles.txtKval,'Visible','off'); %Text invisible.
set(handles.sldKIndex,'visible','off');%Slider invisible.
set(handles.txtKeq0,'visible','off');
set(handles.txtKeqInf,'visible','off');
set(handles.lbRuleDescr,'Value',1); %Display starting at line 1.
set(handles.cbInteract,'visible','off');%Interaction option not visible.
set(handles.cbInteract,'Value',0); %Interaction option is off.
handles.interactive=0; %Save interaction info.
guidata(handles.RLocusGuiFig, handles); %save changes to handles.
%Choose proper function based upon rule chosen. The variable "s" stores the
%string describing the application of the specified rule. Note the last
%two rules are handled differently because they allow interaction with
%user.
switch get(handles.panelChooseRule,'SelectedObject'),
case handles.rbInfo, s=RuleInfo(handles);
case handles.rbSym, s=RuleSymmetry(handles);
case handles.rbNumBranch, s=RuleNumBranch(handles);
case handles.rbStartEnd, s=RuleStartEnd(handles);
case handles.rbRealAxis, s=RuleRealAxis(handles);
case handles.rbAsymptotes, s=RuleAsymptotes(handles);
case handles.rbBreakOutIn, s=RuleBreakOutIn(handles);
case handles.rbDepart, s=RuleDepart(handles);
case handles.rbArrive, s=RuleArrive(handles);
case handles.rbCrossImag, s=RuleCrossImag(handles);
case handles.rbFindGain,
s=RuleFindGain(handles);
set(handles.cbInteract,'visible','on');
set(handles.cbInteract,'string','Specify another pole location?');
case handles.rbLocPos,
s=RuleLocPos(handles);
set(handles.cbInteract,'visible','on');
set(handles.cbInteract,'string','Change K and find roots?');
otherwise
beep
end
set(handles.lbRuleDescr,'String',s); %Display string in window.
guidata(hObject, handles); %save changes to handles.
%--------------------------------------------------------------------------
% --- Executes on button press in pbRuleDetail. ---------------------------
% Depending on which rule is selected, go to appropriate section of web
% page that describes rule.
function pbRuleDetail_Callback(hObject, eventdata, handles)
rt='http://www.swarthmore.edu/NatSci/echeeve1/Ref/LPSA/Root_Locus/DeriveRootLocusRules.html';
switch get(handles.panelChooseRule,'SelectedObject'),
case handles.rbInfo, rt=[rt '#Background'];
case handles.rbSym, rt=[rt '#RuleSym'];
case handles.rbNumBranch, rt=[rt '#RuleNum'];
case handles.rbStartEnd, rt=[rt '#RuleStart'];
case handles.rbRealAxis, rt=[rt '#RuleReal'];
case handles.rbAsymptotes, rt=[rt '#RuleInf'];
case handles.rbBreakOutIn, rt=[rt '#RuleBrk'];
case handles.rbDepart, rt=[rt '#RuleDep'];
case handles.rbArrive, rt=[rt '#RuleArv'];
case handles.rbCrossImag, rt=[rt '#RuleImag'];
case handles.rbFindGain, rt=[rt '#RuleFindPole'];
case handles.rbLocPos, rt=[rt '#RuleFindK'];
otherwise
beep
end
web(rt);
%-------------------------------------------------------------------------
% --- Executes on slider movement.----------------------------------------
function sldKIndex_Callback(hObject, eventdata, handles)
kInd=round(get(hObject,'Value')); %Get index of "K".
set(hObject,'Value',kInd);
%Get necessary values, and select right set of axes.
k=handles.K; r=handles.R; ColOrd=handles.ColorOrder;
axes(handles.axRules); cla;
if handles.interactive, %If GUI is in interactive mode,
handles.kInd=kInd; %Get value;
guidata(hObject, handles); % Update handles structure
s=RuleLocPos(handles); %Get explantory string,
set(handles.lbRuleDescr,'String',s); %...and display it.
end
%Show "K" value on plot.
set(handles.txtKval,'string',sprintf('K = %5.3g ',k(kInd)));
%Draw the locus.
drawRLocus(handles,handles.axRules,1.5,length(handles.K),...
get(handles.txtKval,'string'));
for c=1:size(r,1)
plot(real(r(c,kInd)),imag(r(c,kInd)),'o','MarkerSize',4,...
'MarkerEdgeColor',ColOrd(c,:),'MarkerFaceColor',ColOrd(c,:));
end
%-------------------------------------------------------------------------
% --- Executes on button press in cbInteract------------------------------
% This function is inelegant. It handles the special case when the GUI is
% interactive (i.e., for the last two rules).
function cbInteract_Callback(hObject, eventdata, handles)
if get(handles.cbInteract,'Value'), %If the GUI is interactive.
handles.interactive=1;
guidata(hObject, handles); %...update handles structure to save info.
switch get(handles.panelChooseRule,'SelectedObject'), %Find which rule.
case handles.rbFindGain, %If "Find gain"
s=RuleFindGain(handles); %... get string and display.
set(handles.lbRuleDescr,'String',s);
set(handles.cbInteract,'Value',0); %Clear check box.
case handles.rbLocPos, %If "Find Locus Location"
s=RuleLocPos(handles); %... get string and display.
set(handles.lbRuleDescr,'String',s);
otherwise
beep;
end
else
handles.interactive=0;
guidata(hObject, handles); % Update handles structure
switch get(handles.panelChooseRule,'SelectedObject'),
case handles.rbFindGain
s=RuleFindGain(handles);
set(handles.lbRuleDescr,'String',s);
case handles.rbLocPos
s=RuleLocPos(handles);
set(handles.lbRuleDescr,'String',s);
otherwise
beep;
end
end
guidata(hObject, handles); % Update handles structure
%-------------------------------------------------------------------------
%-------------------------------------------------------------------------
% Take a string "q", and a quantity "x" and generate correct syntax.
% i.e. Plurstr(3,'dog') yields "3 dogs", Plurstr(1,'dog') yields "1 dog"
function s=PlurStr(x,q)
switch(x),
case 0
s=['0 ' q 's'];
case 1
s=['1 ' q];
otherwise
s=sprintf('%g %ss',x,q);
end
%-------------------------------------------------------------------------
%-------------------------------------------------------------------------
% More advanced version of Plurstr that also enumerates elements in a list.
% It takes two strings, s1 and s2, andan array x (and a final puntuation
% mark) and forms a phrase. "x" can be complex.
% e.g. ListString('There exist',[-3 -2 -1],'pole','.')
% yields "There exist 3 poles at s=-3, -2, -1."
function s=ListString(s1,x,s2,punct)
if isempty(x),
s=[s1 '0 ' s2 's' punct];
else
s=[s1 PlurStr(length(x),s2) ' at s =' CmplxString(x)];
s(end)=punct;
end
%--------------------------------------------------------------------------
%--------------------------------------------------------------------------
% Generates a list of properly formatted, and comma separated, complex and
% real numbers from an array z. It assumes complex conjugate pairs are
% consecutive in the list and displays both with a "+/-"
function s=CmplxString(z)
s='';
if isempty(z),
s='(None exist)';
else
i=1;
while i<=length(z),
if isreal(z(i)),
s=sprintf('%s %5.2g, ',s,z(i));
else
s=sprintf('%s %5.2g±%5.2gj, ',s,real(z(i)),imag(z(i)));
i=i+1;
end
i=i+1;
end
end
s(end-1)='';
%--------------------------------------------------------------------------
%--------Draw the root locus----------------------------------------------
function drawRLocus(handles,ax,w,numPts,titleString)
% Retrieve necessary information.
ColOrd=handles.ColorOrder; p=handles.P; z=handles.Z; r=handles.R;
axes(ax); %Select proper axes.
plot(real(p),imag(p),'xk','MarkerSize',10); %Plot poles
hold on
plot(real(z),imag(z),'ok','MarkerSize',10); %Plot zeros
plot([handles.Xmin handles.Xmax],[0 0],'k:');%Plot axes.
plot([0 0],[-handles.Ymax handles.Ymax],'k:');
for c=1:size(r,1), %Plot locus
plot(real(r(c,1:numPts)),imag(r(c,1:numPts)),...
'LineWidth',w,'Color',ColOrd(c,:));
end
box on;
axis([handles.Xmin handles.Xmax -handles.Ymax handles.Ymax]);
title(titleString); xlabel('\sigma (real part of s)');
ylabel('j\omega (imag part of s)');
%-------------------------------------------------------------------------
%--Draw arc at "z" of extent "theta", color "c", radius "r", with label---
% Draw lines from "z" to "s" and horizontally from z.
% Used in RuleArrive, and Rule Depart.
function DrawArc(s,z,c,theta,r,label)
c2=c*3/4;
x=[0 r*cos(linspace(0,theta,10)) 0]; y=[0 r*sin(linspace(0,theta,10)) 0];
patch(x+real(z),y+imag(z),c,...
'EdgeColor',c2,...
'FaceAlpha',0.5,...
'LineStyle',':');
plot([real(z) real(s)],[imag(z) imag(s)],'-.','Color',c2);
plot([real(z) real(z)+2*r],[imag(z) imag(z)],'-.','Color',c2);
text(real(z)+r*cos(theta/2),imag(z)+r*sin(theta/2)...
,label,'Color',c2,'FontSize',9);
%-------------------------------------------------------------------------
%% Web page generation
%--------------------Make web page----------------------------------------
%The variable "hC" hold the Html Code.
function pbMakeWeb_Callback(hObject, eventdata, handles)
%Header.
hC{1}='
Root Locus'; %hC = htmlCode
hC{end+1}='Root Locus
';
hC{end+1}='Transfer function
';
%Add transfer function.
tfString=get(handles.txtXfer,'String');
hC{end+1}=htmlString(['' tfString{1} '
']);
hC{end+1}=htmlString([tfString{2} '
']);
hC{end+1}=htmlString([tfString{3} '
']);
%Background information.
hC{end+1}='
Xfer Function Info
';
%This line adds text generated by rule to hC.
hC=htmlAppend(hC, RuleInfo(handles));
%Completed root locus plot.drawRLocus
hC{end+1}='
Completed Root Locus
';
newFig=figure; newAx=gca;
newFigPos=get(newFig,'Position');
newFigPos(3:4)=[350 350];
set(newFig,'Position',newFigPos);
drawRLocus(handles,newAx,1.5,length(handles.K),'Complete Root Locus');
x=getframe(newFig); imwrite(x.cdata,'RLTotal.png','png');
hC{end+1}='

';
%Symmetry rule.
hC{end+1}='
Root Locus Symmetry
';
%This line adds text generated by rule to hC.
hC=htmlAppend(hC, RuleSymmetry(handles));
%Number of branches.
hC{end+1}='
Number of Branches
';
%This line adds text generated by rule to hC.
hC=htmlAppend(hC, RuleNumBranch(handles));
%Starting and ending points.
hC{end+1}='
Start/End Points
';
%This line adds text generated by rule to hC.
hC=htmlAppend(hC, RuleStartEnd(handles,newAx));
%Axes on real axis rule.
hC{end+1}='
Locus on Real Axis
';
s=RuleRealAxis(handles,newAx); %Get explanatory string.
x=getframe(newFig); %Get plot.
imwrite(x.cdata,'RLRealAxis.png','png'); %Save plot.
hC{end+1}='
';
hC=htmlAppend(hC, s); %Save string.
%Asymptotes (only generate image if appropriate).
hC{end+1}='
';
hC{end}=[hC{end} 'Asymptotes as |s|??
'];
[s,doPlot]=RuleAsymptotes(handles,newAx); %Get explanatory string.
if doPlot, %If plot is necessary, make it and save it.
x=getframe(newFig);
imwrite(x.cdata,'RLAsymptotes.png','png');
hC{end+1}='
';
hC{end}=[hC{end} '
'];
end;
hC{end+1}='';
hC=htmlAppend(hC, s); %Save string.
%Break-out and -in (only generate image if appropriate).
hC{end+1}='
';
hC{end}=[hC{end} 'Break-Out and In Points on Real Axis
'];
s=RuleBreakOutIn(handles,newAx); %Get explanatory string.
x=getframe(newFig); %Get plot and save it
imwrite(x.cdata,'RLBreakOutIn.png','png');
hC{end+1}='
';
hC{end}=[hC{end} ''];
hC=htmlAppend(hC, s);
%Angles of departure (only generate image if appropriate).
hC{end+1}='
Angle of Departure
';
[s,doPlot]=RuleDepart(handles,newAx); %Get explanatory string.
if doPlot, %If plot is necessary, make it and save it.
x=getframe(newFig);
imwrite(x.cdata,'RLDepart.png','png');
hC{end+1}='
';
end;
hC{end+1}='';
hC=htmlAppend(hC, s);
%Angles of arrival (only generate image if appropriate).
hC{end+1}='
Angle of Arrival
';
[s,doPlot]=RuleArrive(handles,newAx); %Get explanatory string.
if doPlot, %If plot is necessary, make it and save it.
x=getframe(newFig);
imwrite(x.cdata,'RLArrive.png','png');
hC{end+1}='
';
end;
hC{end+1}='';
hC=htmlAppend(hC, s);
%Crossing imaginary axis (only generate image if appropriate).
hC{end+1}='
Cross Imag. Axis
';
[s,doPlot]=RuleCrossImag(handles,newAx); %Get explanatory string.
if doPlot, %If plot is necessary, make it and save it.
x=getframe(newFig);
imwrite(x.cdata,'RLCrossImag.png','png');
hC{end+1}='
';
hC{end}=[hC{end} '
'];
end;
hC{end+1}='';
hC=htmlAppend(hC, s);
%Find pole, given K.
hC{end+1}='
';
hC{end}=[hC{end} 'Changing K Changes Closed Loop Poles
'];
s=RuleLocPos(handles,newAx); %Get explanatory string.
x=getframe(newFig); %Get plot and save it
imwrite(x.cdata,'RLLocPos.png','png');
hC{end+1}='
';
hC=htmlAppend(hC, s);
%Find K, given pole.
hC{end+1}='
';
hC{end}=[hC{end} 'Choose Pole Location and Find K
'];
s=RuleFindGain(handles,newAx); %Get explanatory string.
x=getframe(newFig); %Get plot and save it.
imwrite(x.cdata,'RLFindGain.png','png');
hC{end+1}='
';
hC=htmlAppend(hC, s);
hC{end+1}='
'; %end html code.
close(newFig);
fid=fopen('RLocus.html','w'); %Open html file.
for i=1:length(hC),
fprintf(fid,'%s\r',hC{i}); %Save each line of code.
end
fclose(fid);
web('RLocus.html'); %Display web page.
%-------------------------------------------------------------------------
%-------------------------------------------------------------------------
% Convert double spaces (necessary b/c html ignores whitespace.
function s1=htmlString(s)
s1=regexprep(s,' ',' ');
%-------------------------------------------------------------------------
%---------function to append a string to html code------------------------
%c1 holds current html code, c2 holds code to be added.
function c3=htmlAppend(c1,c2)
c3=c1;
for i=1:length(c2),
c3{end+1}=[c2{i} '
']; %Add a line break.
end
%-------------------------------------------------------------------------
%% Root Locus Rules
% This code cell holds functions that explain (via a string "s") and
% demonstrate (where appropriate) via a graph, the rule in question.
%------------------------------Get TF information-------------------------
% Get TF information and display it - no graph is created.
function s=RuleInfo(handles)
%Turn off axes.
axes(handles.axRules); cla; set(handles.axRules,'Visible','off');
% Load all necessary information.
m=handles.M; n=handles.N; q=handles.Q;
z=handles.Z; p=handles.P;
s{1}='For the open loop transfer function, G(s)H(s):';
s{end+1}=ListString('We have n=', p, 'pole','.');
s{end+1}=ListString('We have m=', z, 'finite zero','.');
s{end+1}=['So there exists q=' PlurStr(q,'zero') ' as |s| goes to infinity'];
s{end}=[s{end} sprintf(' (q = n-m = %g-%g = %g).',n,m,q)];
s{end+1}=' ';
s{end+1}='We can rewrite the open loop transfer function as';
s{end+1}='G(s)H(s)=N(s)/D(s) where N(s) is the numerator polynomial, and';
s{end+1}='D(s) is the denominator polynomial. ';
s{end+1}=['N(s)=' poly2str(handles.Num,'s') ', and'];
s{end+1}=['D(s)=' poly2str(handles.Den,'s') '.'];
s{end+1}=' ';
s{end+1}='Characteristic Equation is 1+KG(s)H(s)=0, or 1+KN(s)/D(s)=0,';
s{end+1}=['or D(s)+KN(s) = ' poly2str(handles.Den,'s') '+ K(' ...
poly2str(handles.Num,'s') ' ) = 0'];
%-------------------------------------------------------------------------
%-----------------Describe symmetry---------------------------------------
function s=RuleSymmetry(handles)
%Axes off
axes(handles.axRules); cla; set(handles.axRules,'Visible','off')
s{1}='As you can see, the locus is symmetric about the real axis';
%-------------------------------------------------------------------------
%-----------------Number of branches--------------------------------------
function s=RuleNumBranch(handles)
% No graph
axes(handles.axRules); cla; set(handles.axRules,'Visible','off')
n=handles.N;
s{1}='The open loop transfer function, G(s)H(s), has ';
s{1}=[s{1} sprintf('%g poles, therefore the',n)];
s{2}=sprintf('locus has %g branches.',n);
s{3}=' ';
s{4}='Each branch is displayed in a different color.';
%-------------------------------------------------------------------------
%---------------Starting and ending points (i.e., poles and zeros)---------
function s=RuleStartEnd(handles,ax)
% The second argument is really a dummy argument - if it is there, it does
% not create a graph (this is done for web page). If it is not there, it
% shows locus on right axis and allows for user interaction.
del=0.02; %Delay (for animation)
%Get pertinent information.
n=handles.N; m=handles.M; q=handles.Q; p=handles.P;
z=handles.Z; k=handles.K; r=handles.R; ColOrd=handles.ColorOrder;
%Show axes.
axes(handles.axRules); cla;
if nargin==1, %Plot on right axis in GUI.
plot(real(p),imag(p),'xk','MarkerSize',10);
hold on
plot(real(z),imag(z),'ok','MarkerSize',10);
plot([handles.Xmin handles.Xmax],[0 0],'k:');
plot([0 0],[-handles.Ymax handles.Ymax],'k:');
axis([handles.Xmin handles.Xmax -handles.Ymax handles.Ymax]);
set(handles.txtKval,'Visible','on');
set(handles.txtKval,'String','K = 0');
end
s{1}='Root locus starts (K=0) at poles of open ';
s{1}=[s{1} 'loop transfer function, G(s)H(s).'];
s{2}='These are shown by an "x" on the diagram above';
s{3}=' ';
if nargin==1, %Plot on right axis in GUI.
set(handles.lbRuleDescr,'String',s);
s{end+1}='As K increases, the location of closed loop poles move, as';
s{end+1}='shown on diagram (the value of k is in upper left corner).';
s{end+1}=' ';
set(handles.lbRuleDescr,'String',s);
for k1=2:length(k), %This loop creates an animation of the movement of
%the locus as K varies.
drawRLocus(handles,handles.axRules,1.5,k1,'Locus start/end points');
set(handles.txtKval,'string',sprintf('K = %5.3g ',k(k1)));
for c=1:size(r,1)
plot(real(r(c,k1)),imag(r(c,k1)),'o',...
'MarkerSize',4,...
'MarkerEdgeColor',ColOrd(c,:),...
'MarkerFaceColor',ColOrd(c,:));
end
hold off
pause(del);
end
end
s{end+1}='As K goes to infinity the location of closed loop poles move';
s{end+1}='to the zeros of the open loop transfer function, G(s)H(s).';
if m~=0, %There are some finite zeros.
s{end+1}='Finite zeros are shown by a "o" on the diagram above.';
end
if q~=0, %There are some zeros at infinity.
s{end+1}=['Don''t forget we have ' 'we also have q=n-m='];
s{end}=[s{end} PlurStr(q,'zero') ' at infinity.'];
s{end+1}=['(We have n=' PlurStr(n,'finite pole') ', '];
s{end}=[s{end} 'and m=' PlurStr(m,'finite zero') ').'];
end
if nargin==1, %Show slider for user interaction.
s{end+1}=' ';
s{end+1}='Use slider to change K and see resulting location of roots.';
s{end+1}=' ';
s{end+1}='To redo animation, deselect button (at left), then reselect.';
set(handles.sldKIndex,'visible','on');
set(handles.txtKeq0,'visible','on');
set(handles.txtKeqInf,'visible','on');
set(handles.sldKIndex,'value',get(handles.sldKIndex,'Max'));
end
%-------------------------------------------------------------------------
%--------Show locus on real axis------------------------------------------
function s=RuleRealAxis(handles,ax)
%Get pertinent information
cHiLt=handles.HighlightColor; p=handles.P; z=handles.Z;
%Determine which axes to use (GUI axes if no second argument, and a
%separate set of axes if there is a second argument - this is done for
%web page).
if nargin==1,
ax=handles.axRules;
end
axes(ax); cla; hold on;
s{1}='The root locus exists on real axis to left of an odd number of';
s{2}='poles and zeros of open loop transfer function, G(s)H(s), that are';
s{3}='on the real axis.';
s{4}='These real pole and zero locations are highlighted on diagram,';
s{5}='along with the portion of the locus that exists on the real axis.';
s{6}=' ';
% Find all poles and zero on real axis.
axisPoles=flipud(find(imag(p)==0)); %Flip to go highest->lowest.
axisZeros=flipud(find(imag(z)==0));
if length(axisPoles)~=0, %We have poles on axis - highlight them.
plot(p(axisPoles),0,'s',...
'MarkerSize',12,...
'MarkerEdgeColor',cHiLt,...
'MarkerFaceColor',cHiLt);
end
if length(axisZeros~=0), %We have zeros on axis - highlight them.
plot(z(axisZeros),0,'d',...
'MarkerSize',12,...
'MarkerEdgeColor',cHiLt,...
'MarkerFaceColor',cHiLt);
end
%Put all poles and zeros that are on the axis in a single list, sorted.
lst=sort([p(axisPoles); z(axisZeros)],'descend');
if isempty(lst), % If no elements on axis, no locus on axis.
s{end+1}='No poles or zeros on axis, so locus does not';
s{end+1}='exist along axis.';
else % The locus exists between every other element on axis.
s{end+1}='Root locus exists on real axis between:';
for i=1:2:length(lst),
if i==length(lst),
s{end+1}=sprintf(' %5.2g and negative infinity',lst(i));
plot([lst(i) handles.Xmin],[0 0],'Linewidth',6,'Color',cHiLt);
else
s{end+1}=sprintf(' %5.2g and %5.2g',lst(i),lst(i+1));
plot([lst(i) lst(i+1)],[0 0],'Linewidth',6,'Color',cHiLt);
end
end
end
s{end+1}=' ';
s{end+1}='... because on the real axis,';
s{end+1}=ListString(' we have ', p(axisPoles), 'pole',',');
s{end+1}=ListString(' and we have ', z(axisZeros), 'zero','.');
%Draw the locus.
drawRLocus(handles,ax,1.5,length(handles.K),'Locus on Real Axis');
%-------------------------------------------------------------------------
%----------------Find (and show) asymptotes-------------------------------
function [s,doPlot]=RuleAsymptotes(handles, ax)
% Get pertinent information
q=handles.Q; n=handles.N; m=handles.M; p=handles.P; z=handles.Z;
cHiLt=handles.HighlightColor;
s{1}=' ';
s{1}='In the open loop transfer function, G(s)H(s),';
s{1}=[s{1} ' we have n=' PlurStr(n,'finite pole') ','];
s{2}=['and m=' PlurStr(m,'finite zero') ', therefore '];
s{2}=[s{2} 'we have q=n-m=' PlurStr(q,'zero') ' at infinity.'];
s{3}=' ';
if q==0, %If there are no asymptotes (q=0) we're done, make no plot.
doPlot=0;
axes(handles.axRules); cla; set(handles.axRules,'Visible','off')
s{end+1}='Because q=0, there are no asymptotes.';
return
end
doPlot=1; %Make a plot on specified axes (GUI if no second argument to
%... function.
if nargin==1,
ax=handles.axRules;
end
axes(ax); cla; hold on;
%Do calcluations.
sump=sum(p); sumz=sum(z); %Sum of poles and zeros.
sigma=real(sump-sumz)/q; %Intersect on real axis.
theta=180/q; %Angle of asymptotes
s{end+1}='Angle of asymptotes at odd multiples of ±180°/q ';
eStr=['(i.e., ' sprintf(' ±%g°,',(1:2:q)*180/q)];
s{end}=[s{end} eStr(1:(end-1)) ').']; %Strip off last comma.
s{end+1}=' ';
s{end+1}=ListString('There exists ', p, ' pole',',');
s{end+1}=sprintf(' ...so sum of poles=%g.',sump);
s{end+1}=ListString('There exists ', z, ' zero',',');
s{end+1}=sprintf(' ...so sum of zeros=%g.',sumz);
s{end+1}='(Imaginary components of poles and zeros, if any, cancel when';
s{end+1}=' summed because they appear as complex conjugate pairs.)';
s{end+1}=' ';
s{end+1}='Asymptote intersect is at ( (sum of poles)-(sum of zeros) )/q';
s{end+1}=sprintf('Intersect is at ((%g)-(%g))/%g = %g/%g = %5.3g',...
sump,sumz,q,sump-sumz,q,sigma);
s{end}=[s{end} ' (highlighted by five pointed star).'];
if q==1,
s{end+1}='Since q=1, there is a single asymptote at ±180°';
s{end+1}='(on negative real axis), so intersect of this asymptote';
s{end+1}='on the axis s not important (but it is shown anyway).';
end
plot(sigma,0,'p',... %Plot intersect with big start.
'MarkerSize',16,...
'MarkerEdgeColor',cHiLt,...
'MarkerFaceColor',cHiLt);
radius=max(get(gca,'YLim'))*5; %Make radius big enough to go off page.
for i=0:(q-1),
theta_i=(2*i+1)*theta*pi/180; %Plot asymptotes.
line([sigma sigma+radius*cos(theta_i)],...
[0 radius*sin(theta_i)],...
'Color',cHiLt,...
'LineStyle','--',...
'LineWidth',4);
end
%Draw root locus.
drawRLocus(handles,ax,1.5,length(handles.K),...
'Asymptotes as |s| goes to infinity');
%-------------------------------------------------------------------------
%--------------Break-out and break-in.
function s=RuleBreakOutIn(handles, ax)
s{1}='';
cHiLt=handles.HighlightColor;
if nargin==1,
ax=handles.axRules;
end
axes(ax); cla; hold on;
num=handles.Num; %N(s)
den=handles.Den; %D(s)
np=polyder(num); %N'(s)
dp=polyder(den); %D'(s)
p1=conv(np,den); %p1=N'(s)D(s)
p2=conv(dp,num); %p2=D'(s)N(s)
np1=length(p1); %Next lines make p1 and p2 the same order.
np2=length(p2);
if np1>np2,
p2=[zeros(1,np1-np2) p2];
elseif np2>np1
p1=[zeros(1,np2-np1) p1];
end
pn=p2-p1; %pn=D'(s)N(s)-N'(s)D(s)
baway=roots(pn); %breakaway are at roots of pn.
s{1}='Break Out (or Break In) points occur where ';
s{1}=[s{1} 'N(s)D''(s)-N''(s)D(s)=0, or'];
s{end+1}=[poly2str(pn,'s') ' = 0. (details below*) '];
s{end+1}=' ';
s{end+1}=ListString('This polynomial has ', baway, 'root','.');
s{end+1}=' ';
% In the next loop, we determine the value of K at each root of "pn". If K
% is real and positive, the point is on the locus. If the point is also
% real, then it is one of our points.
realK=[]; %This will hold all breakaway location that occur at real
%...values of K,
posRealK=[]; %...this holds only those for positive real K.
for i=1:length(baway),
%Find value of K at the baway location.
kVal=-polyval(den,baway(i))/polyval(num,baway(i));
if isreal(kVal),
realK=[realK baway(i)];
if kVal>=0, %If K is real and positive, this point is on locus
%...so plot it with a square,
posRealK=[posRealK baway(i)];
plot(real(baway(i)),imag(baway(i)),'s',...
'MarkerSize',10,...
'MarkerEdgeColor',cHiLt,...
'MarkerFaceColor',cHiLt);
else
%...else, plot it with a diamond (negative value of K).
plot(real(baway(i)),imag(baway(i)),'d',...
'MarkerSize',10,...
'MarkerEdgeColor',cHiLt,...
'MarkerFaceColor',cHiLt);
end
end
end
%Draw the root locus plot.
drawRLocus(handles,ax,1.5,length(handles.K),...
'Break-away and Break-in points on real axis');
s{end+1}=['From these ' PlurStr(length(baway),'root')];
s{end}=[s{end} ListString( ', there exists ',realK,'real root','.')];
s{end+1}='These are highlighted on the diagram above (with squares ';
s{end}=[s{end} 'or diamonds.)'];
s{end+1}=' ';
if length(posRealK)~=length(realK),
if isempty(posRealK),
s{end+1}='None of the roots are on the locus.';
else
s{end+1}='Not all of these roots are on the locus. ';
s{end}=[s{end} 'Of these ' PlurStr(length(realK),'real root') ','];
s{end+1}=ListString( 'there exists ',posRealK,'root',' ');
s{end}=[s{end} 'on the locus (i.e., K>0).'];
s{end+1}='Break-away (or break-in) points on the locus are shown ';
s{end}=[s{end} 'by squares.'];
s{end+1}=' ';
s{end+1}='(Real break-away (or break-in) with K less than 0 are';
s{end}=[s{end} ' shown with diamonds).'];
end
else
s{end+1}='These roots are all on the locus (i.e., K>0), ';
s{end}=[s{end} 'and are highlighted with squares.'];
end
s{end+1}=' ';
s{end+1}='* N(s) and D(s) are numerator and denominator polynomials';
s{end+1}='of G(s)H(s), and the tick mark, '', denotes differentiation.';
s{end+1}=['N(s) =' poly2str(num,'s')];
s{end+1}=['N''(s) =' poly2str(np,'s')];
s{end+1}=['D(s)=' poly2str(den,'s')];
s{end+1}=['D''(s)=' poly2str(dp,'s')];
s{end+1}=['N(s)D''(s)=' poly2str(p2,'s')];
s{end+1}=['N''(s)D(s)=' poly2str(p1,'s')];
s{end+1}=['N(s)D''(s)-N''(s)D(s)=' poly2str(pn,'s')];
s{end+1}=' ';
s{end+1}='Here we used N(s)D''(s)-N''(s)D(s)=0, but we could multiply';
s{end+1}='by -1 and use N''(s)D(s)-N(s)D''(s)=0.';
%------------------------------------------------------------------------
%-------------------------Angle of departure-----------------------------
function [s, doPlot]=RuleDepart(handles,ax)
cmplxPole=handles.cmplxPole; %Get all the complex poles
if isempty(cmplxPole), %... if none, we are done.
axes(handles.axRules); cla; set(handles.axRules,'Visible','off')
s{1}='No complex poles in loop gain, so no angles of departure.';
doPlot=0;
return
end
doPlot=1;
% Get pertinent information, and clear appropriate axes
z=handles.Z; p=handles.P; k=handles.K; cHiLt=handles.HighlightColor;
if nargin==1,
ax=handles.axRules;
end
axes(ax); cla;
%Draw the root locus.
drawRLocus(handles,ax,1.5,length(k),'Angle of departure shown in gray');
s='';
if length(cmplxPole)>1, %This is laborious, do in only once.
s{1}='Loop gain has more than one pair of complex poles. We will do';
s{2}='analysis for only one pair. Others are left as an exercise.';
s{3}=' ';
end;
cP=cmplxPole(1); %Choose the complex conjugate to analyze.
s{end+1}=['Find angle of departure from pole at ' num2str(cP)];
s{end+1}=' Note: Theta_p2 denotes angle labeled theta with subscript p2.';
s{end+1}=' ';
% To understand the following, it is best to do an example and examine the
% resulting plot.
r=(handles.Xmax-handles.Xmin)/15; %Radius for arcs drawn on plot.
sum_zeros=0;
for i=1:length(z), %For each zero...
theta=angle(cP-z(i)); %...find the angle to the chosen pole,
sum_zeros=sum_zeros+theta; %...find sum of angles,
%...draw arc on plot to show angle.
DrawArc(cP,z(i),cHiLt,theta,r,['\theta_{z' num2str(i) '}']);
if i==1, %Give more detail with first one, others will be succinct.
s{end+1}=['Theta_z' num2str(i) '=angle((Departing pole)'...
'- (zero at ' num2str(z(i)) ') ).'];
end
fs=['Theta_z' num2str(i)...
'=angle((%s) - (%s)) = angle(%s) = %s°'];
s{end+1}=sprintf(fs,num2str(cP),num2str(z(i)),...
num2str(cP-z(i)),num2str(theta*180/pi));
end
s{end+1}=' ';
sum_poles=0;
firstLine=1; %Again, we will be more verbose with first angle, but this
%...may not be p(1), so this variable indicates first time
%...through loop. Otherwise this code is almost identical
%...to that above.
for i=1:length(p),
if p(i)~=cP, %Only examine angle to poles other than the one from
%...which we are trying to find the angle of departure.
theta=angle(cP-p(i));
sum_poles=sum_poles+theta;
DrawArc(cP,p(i),cHiLt,theta,r,['\theta_{p' num2str(i) '}']);
if firstLine==1,
s{end+1}=['Theta_p' num2str(i) '=angle((Departing pole)'...
'- (pole at ' num2str(p(i)) ') ).'];
firstLine=0;
end
fs=['Theta_p' num2str(i)...
'=angle((%s) - (%s)) = angle(%s) = %s°'];
s{end+1}=sprintf(fs,num2str(cP),num2str(p(i)),...
num2str(cP-p(i)),num2str(theta*180/pi));
end
end
s{end+1}=' ';
theta_D=pi+sum_zeros-sum_poles; %Formula for angle of departure.
theta_D1=theta_D;
while theta_D<=-pi, %...it should be more then -pi
theta_D=theta_D+2*pi;
end
while theta_D>=pi, %...and less than pi.
theta_D=theta_D-2*pi;
end
s{end+1}='Angle of Departure is equal to:';
s{end+1}='Theta_depart = 180° + sum(angle to zeros) - ';
s{end}=[s{end} 'sum(angle to poles).'];
s{end+1}=['Theta_depart = 180° + ' num2str(sum_zeros*180/pi)...
'-' num2str(sum_poles*180/pi) '.'];
s{end+1}=sprintf('Theta_depart = %5.3g°.',theta_D1*180/pi);
if theta_D1 ~= theta_D,
s{end+1}=sprintf('This is equivalent to %5.3g°.',theta_D*180/pi);
end
s{end+1}=' ';
s{end+1}='This angle is shown in gray.';
s{end+1}='It may be hard to see if it is near zero°.';
%Draw angle of departure with a larger (grey) arc.
r=2*r;
DrawArc(cP+r*exp(j*theta_D),cP,[0.8 0.8 0.8],theta_D,r,'\theta_{depart}');
%-------------------------------------------------------------------------
%------Angle of arrival---------------------------------------------------
%This code is so similar to that for the angle of departure, that it does
%no warrant its own set of comments.
function [s, doPlot]=RuleArrive(handles,ax)
cmplxZero=handles.cmplxZero;
if isempty(cmplxZero),
axes(handles.axRules); cla; set(handles.axRules,'Visible','off')
s{1}='No complex zeros in loop gain, so no angles of arrival.';
doPlot=0;
return
end
doPlot=1;
z=handles.Z; p=handles.P; k=handles.K; cHiLt=handles.HighlightColor;
if nargin==1,
ax=handles.axRules;
end
axes(ax); cla;
drawRLocus(handles,ax,1.5,length(k),'Angle of arrival shown in gray');
s='';
if length(cmplxZero)>1,
s{1}='Loop gain has more than one pair of complex zeros. We will do';
s{2}='analysis for only one pair. Others are left as an exercise.';
s{3}=' ';
end;
chosenZero=1;
cZ=cmplxZero(chosenZero); %Choose the complex conjugate to analyze.
s{end+1}=['Find angle of arrival to pole at ' num2str(cZ)];
s{end+1}=' Note: Theta_p2 denotes angle labeled theta with subscript p2.';
s{end+1}=' ';
r=(handles.Xmax-handles.Xmin)/15;
sum_zeros=0;
FirstLine=1;
for i=1:length(z),
if z(i)~=cZ,
theta=angle(cZ-z(i));
sum_zeros=sum_zeros+theta;
DrawArc(cZ,z(i),cHiLt,theta,r,['\theta_{z' num2str(i) '}']);
if FirstLine==1,
s{end+1}=['Theta_z' num2str(i) ...
'=angle( (Arriving zero) - (zero at ' num2str(p(i)) ') ).'];
end
fs=['Theta_z' num2str(i)...
'=angle((%s) - (%s)) = angle(%s) = %s°'];
s{end+1}=sprintf(fs,num2str(cZ),num2str(z(i)),...
num2str(cZ-z(i)),num2str(theta*180/pi));
end
end
s{end+1}=' ';
sum_poles=0;
for i=1:length(p),
theta=angle(cZ-p(i));
sum_poles=sum_poles+theta;
DrawArc(cZ,p(i),cHiLt,theta,r,['\theta_{p' num2str(i) '}']);
if i==1,
s{end+1}=['Theta_p' num2str(i) '=angle( (Arriving zero) - '...
'(pole at ' num2str(p(i)) ') ).'];
end
fs=['Theta_p' num2str(i)...
'=angle((%s) - (%s)) = angle(%s) = %s°'];
s{end+1}=sprintf(fs,num2str(cZ),num2str(p(i)),...
num2str(cZ-p(i)),num2str(theta*180/pi));
end
s{end+1}=' ';
theta_D=pi-sum_zeros+sum_poles;
theta_D1=theta_D;
while theta_D<=-pi,
theta_D=theta_D+2*pi;
end
while theta_D>=pi,
theta_D=theta_D-2*pi;
end
s{end+1}='Angle of arrival is equal to:';
s{end+1}='Theta_arrive = 180° - sum(angle to zeros) + ';
s{end}=[s{end} 'sum(angle to poles).'];
s{end+1}=['Theta_arrive = 180° - ' num2str(sum_zeros*180/pi)...
'+' num2str(sum_poles*180/pi) '.'];
s{end+1}=sprintf('Theta_arrive = %5.3g°.',theta_D1*180/pi);
if theta_D1 ~= theta_D,
s{end+1}=sprintf('This is equivalent to %5.3g°.',theta_D*180/pi);
end
s{end+1}=' ';
s{end+1}='This angle is shown in gray.';
s{end+1}='It may be hard to see if it is near 0°.';
r=2*r;
DrawArc(cZ+r*exp(j*theta_D),cZ,[0.8 0.8 0.8],theta_D,r,'\theta_{arrive}');
drawRLocus(handles,ax,1.5,length(k),'Angle of arrival shown in gray');
%-------------------------------------------------------------------------
%--------------------Crossing the Imaginary axis-------------------------
function [s,doPlot]=RuleCrossImag(handles, ax)
% Get pertinent infofmation.
k=handles.K; ColOrd=handles.ColorOrder; r=handles.R;
n=0; %n counts the number of values of k that cause crossing of axis
% in top half of s-plane (including real axis).
m=0; %m keeps track of crossings in bottom half of s-plane (not
% including real axis.
%Determine where (and if) the locus crosses the imaginary axis.
for i=1:size(r,1),
for j=1:(length(k)-2), %Don't include last point (often equals Inf)
%Check to see if locus has crossed the imaginary axis.
x1=real(r(i,j)); x2=real(r(i,j+1));
% if (x1<=0 && x2>0) || (x1>0 && x2<0),
if (x1*x2)<=0, %x1=0, x2=0, or x1 and x2 have different signs.
%Only need to check for top half of s plane (and real axis),
%because roots appear in complex conjugate pairs.
if imag(r(i,j))>=0,
n=n+1;
%kcross is approximate value of k where locus crosses axis,
kcross(n)=interp1([x1 x2],[k(j) k(j+1)],0,'linear');
%...wcross is approsimate value of frequency (omega).
wcross(n)=interp1([x1 x2],[imag(r(i,j)) imag(r(i,j+1))],...
0,'linear');
lcross(n)=i; %keep track of which locus (for color on plot).
else
m=m+1;
kcross2(m)=interp1([x1 x2],[k(j) k(j+1)],0,'linear');
wcross2(m)=interp1([x1 x2],[imag(r(i,j)) imag(r(i,j+1))],...
0,'linear');
lcross2(m)=i;
end
end
end
end
if n==0,
doPlot=0;
axes(handles.axRules); cla; set(handles.axRules,'Visible','off')
s{1}='Locus does not cross imaginary axis.';
else
doPlot=1;
if nargin==1,
ax=handles.axRules;
end
axes(ax); cla;
drawRLocus(handles,ax,1.5,length(k)','Locus Crossing Axis');
s{1}=['Locus crosses imaginary axis at ' PlurStr(n,'value') ' of K.'];
s{2}='These values are normally determined by using Routh''s method.';
s{3}='This program does it numerically, and so is only an estimate.';
s{end+1}=' ';
s{end+1}=['Locus crosses where K =' sprintf(' %5.3g,',kcross)];
s{end+1}='corresponding to crossing imaginary axis at s=';
for i=1:n,
if wcross(i)==0,
s{end}=[s{end} ' 0,'];
else
s{end}=[s{end} sprintf(' ±%5.3gj,',wcross(i))];
end
end
if n>1,
s{end}=[s{end} ' respectively.'];
else
s{end}(end)='.';
end
s{end+1}=' ';
s{end+1}='These crossings are shown on plot.';
for c=1:n %Plot each crossing of axis, along with value of K.
plot(0,wcross(c),'o',...
'MarkerSize',8,...
'MarkerEdgeColor',ColOrd(lcross(c),:),...
'MarkerFaceColor',ColOrd(lcross(c),:));
text(-0.25,wcross(c),sprintf('K=%5.3g',kcross(c)),...
'Color',ColOrd(lcross(c),:),...
'HorizontalAlignment','Right',...
'VerticalAlignment','bottom');
end
for c=1:m
plot(0,wcross2(c),'o',...
'MarkerSize',8,...
'MarkerEdgeColor',ColOrd(lcross2(c),:),...
'MarkerFaceColor',ColOrd(lcross2(c),:));
text(-0.25,wcross2(c),sprintf('K=%5.3g',kcross2(c)),...
'Color',ColOrd(lcross2(c),:),...
'HorizontalAlignment','Right',...
'VerticalAlignment','top');
end
end
%------------------------------------------------------------------------
%-------Find K given location of roots-----------------------------------
function s=RuleFindGain(handles, ax)
%Set up axes and get pertinent information (as in functions above)
if nargin==1,
ax=handles.axRules;
end
axes(ax); cla;
k=handles.K; r=handles.R; cHiLt=handles.HighlightColor;
num=handles.Num; den=handles.Den;
if handles.interactive,
set(handles.panelChooseRule,'visible','off');
drawRLocus(handles,ax,1.5,length(k),'Choose location for pole.');
s{1}='Choose spot on locus (on rightmost set of axes) to place a';
s{2}='closed loop pole with crosshairs.';
set(handles.lbRuleDescr,'String',s);
[x,y]=ginput(1);
s1=complex(x,y);
set(handles.panelChooseRule,'visible','on');
else
%Choose a value of s to demonstrate.
%Try to find a value of s that is complex (with zeta near 0.7) to
%illustrate the point described above. We will search locus for the
%midrange values of k.
kStart=round(length(k)/4); %Choose value of k near the beginning.
kEnd=round(3*length(k)/4); %Choose value of k near the end.
%Search locus for point near zeta=0.7.
rKeep=0;
bestZeta=Inf;
for k1=kStart:kEnd,
for r1=1:size(r,1),
z=-real(r(r1,k1))/abs(r(r1,k1)); %Definition of zeta;
if ((abs(z-0.7)0),
bestZeta=z;
rKeep=r1;
kKeep=k1;
end
end
end
if bestZeta>0.99, %We didn't find any complex poles, so
rKeep=1; %arbitrarily choose first locus, and
kKeep=round(length(k)/2); %arbitrarily choose middle k value.
end;
s0=r(rKeep,kKeep); %Choose value of s0 on locus.
s1=s0*1.05; %Purposely choose value of s not quite on locus
end
if abs(imag(s1))<0.001, %If close to real, make it real.
s1=real(s1);
end
dVal=polyval(den,s1);
nVal=polyval(num,s1);
kVal=-dVal/nVal;
s{1}='Characteristic Equation is 1+KG(s)H(s)=0, or 1+KN(s)/D(s)=0, or';
s{end+1}=['K = -D(s)/N(s) = -(' poly2str(den,'s') ' ) / ('...
poly2str(num,'s') ' )'];
s{end+1}='We can pick a value of s on the locus, and find K=-D(s)/N(s).';
s{end+1}=' ';
if isreal(s1),
s{end+1}=sprintf('For example if we choose s=%5.3g,',s1);
s{end+1}=sprintf('then D(s)=%5.3g, N(s)=%5.3g,',dVal,nVal);
s{end+1}=sprintf('and K=-D(s)/N(s)=%5.3g.',kVal);
else
s{end+1}=sprintf('For example if we choose s=%5.2g + %5.2gj',...
real(s1),imag(s1));
s{end}=[s{end} ' (marked by asterisk),'];
s{end+1}=sprintf('then D(s)=%5.3g + %5.3gj,',real(dVal),imag(dVal));
s{end}=[s{end}...
sprintf(' N(s)=%5.3g + %5.3gj,',real(nVal),imag(nVal))];
s{end+1}=sprintf('and K=-D(s)/N(s)=%5.3g + %5.3gj.',...
real(kVal),imag(kVal));
s{end+1}='This s value is not exactly on the locus, so K is complex,';
kVal=real(kVal);
s{end+1}=sprintf('(see note below), pick real part of K (%5.3g)',kVal);
end
np1=length(num);
np2=length(den);
ce=den+kVal*[zeros(1,np1-np2) num];
s2=roots(ce);
drawRLocus(handles,ax,1.5,length(k)',...
sprintf('Design for pole at s=%5.3g + %5.3g',real(s1),imag(s1)));
for i=1:length(s2), %Plot resulting roots.
plot(real(s2(i)),imag(s2(i)),'o','MarkerSize',8,...
'MarkerEdgeColor',cHiLt,'MarkerFaceColor',cHiLt);
end
plot(real(s1),imag(s1),'*','MarkerSize',8,...
'MarkerEdgeColor',cHiLt*0.67,'MarkerFaceColor',cHiLt*0.67);
s{end+1}=' ';
s{end+1}=ListString('For this K there exist ',s2,...
'closed loop pole','.');
s{end+1}='These poles are highlighted on the diagram with dots, the value';
s{end+1}='of "s" that was originally specified is shown by an asterisk.';
if nargin==1,
s{end+1}=' ';
s{end+1}='Check the box above if you would like to specify another';
s{end+1}='pole location, and use it to calculate the corresonding';
s{end+1}='value of gain, K.';
end
s{end+1}=' ';
s{end+1}='Note: it is often difficult to choose a value of s that is';
s{end+1}='precisely on the locus, but we can pick a point that is close.';
s{end+1}='If the value is not exactly on the locus, then the calculated';
s{end+1}='value of K will be complex instead of real. Just ignore the';
s{end+1}='the imaginary part of K (which will be small).';
s{end+1}=' ';
s{end+1}='Note also that only one pole location was chosen and this';
s{end+1}='determines the value of K. If the system has more than one';
s{end+1}='closed loop pole, the location of the other poles are';
s{end+1}='determine solely by K, and may be in undesirable locations.';
%-------------------------------------------------------------------------
%------------Given K, determine location of roots-------------------------
function s=RuleLocPos(handles, ax)
%Set appropriate axes, and get pertinent info (as in functions above)
if nargin==1,
ax=handles.axRules;
end
axes(ax); cla;
ColOrd=handles.ColorOrder; k=handles.K; r=handles.R;
num=handles.Num; den=handles.Den;
s{1}='Characteristic Equation is 1+KG(s)H(s)=0, or 1+KN(s)/D(s)=0,';
s{end+1}=['or D(s)+KN(s) = ' poly2str(den,'s') '+ K(' ...
poly2str(num,'s') ' ) = 0'];
s{end+1}=' ';
s{end+1}='So, by choosing K we determine the characteristic equation';
s{end+1}='whose roots are the closed loop poles.';
s{end+1}=' ';
if handles.kInd==0,
kInd=round(length(k)/2); %Choose value of k in middle of the range.
else
kInd=handles.kInd;
end
kVal=k(kInd);
s{end+1}=sprintf('For example with K=%g, then the characteristic',kVal);
s{end}=[s{end} ' equation is'];
s{end+1}=['D(s)+KN(s) = ' poly2str(den,'s') ' + ' ...
num2str(kVal) '(' poly2str(num,'s') ' ) = 0, or'];
np1=length(num);
np2=length(den);
ce=den+kVal*[zeros(1,np1-np2) num];
s{end+1}=[poly2str(ce,'s') '= 0'];
s{end+1}=' ';
s{end+1}=ListString('This equation has ', roots(ce), 'root','.');
s{end+1}='These are shown by the large dots on the root locus plot';
drawRLocus(handles,ax,1.5,length(k)',['Roots at K=' num2str(kVal)]);
for c=1:size(r,1)
plot(real(r(c,kInd)),imag(r(c,kInd)),'o',...
'MarkerSize',6,...
'MarkerEdgeColor',ColOrd(c,:),...
'MarkerFaceColor',ColOrd(c,:));
end
if handles.interactive,
s{end+1}=' ';
s{end+1}='Use slider to change K from this initial value and see the';
s{end+1}='resulting location of the closed loop poles (roots of';
s{end+1}='characteristic equation).';
s{end+1}=' Note: this allows you to choose a large range of values';
s{end+1}=' between 0 and infinity. For larger values of K the roots';
s{end+1}=' may not be visible due to scaling of axes of the graph.';
set(handles.txtKval,'string',sprintf('K =%5.3g',kVal));
set(handles.txtKval,'Visible','on');
set(handles.sldKIndex,'visible','on');
set(handles.txtKeq0,'visible','on');
set(handles.txtKeqInf,'visible','on');
set(handles.sldKIndex,'value',kInd);
else
if nargin==1,
s{end+1}=' ';
s{end+1}='Check the box above if you would like to change the value';
s{end+1}='of K and see the resulting roots.';
set(handles.txtKval,'string',sprintf('K =%5.3g',kVal));
set(handles.txtKval,'Visible','on');
else
set(handles.txtKval,'Visible','off');
set(handles.sldKIndex,'visible','off');
set(handles.txtKeq0,'visible','off');
set(handles.txtKeqInf,'visible','off');
end
end
%-------------------------------------------------------------------------