From: <mur...@us...> - 2010-11-06 16:26:26
|
Revision: 30 http://python-control.svn.sourceforge.net/python-control/?rev=30&view=rev Author: murrayrm Date: 2010-11-06 16:26:19 +0000 (Sat, 06 Nov 2010) Log Message: ----------- Latest version of Ryan Krauss's control package. Significant changes (from git commit log): 2010-10-26 added simplify method to cancel poles and zeros at the same location 2010-10-08 Commented out one line for mpl compatability (new version of mpl) 2010-09-10 Added a simplify method to the TF class to cancel poles and zeros that are with a certain tolerance of one another 2010-09-01 Bug fix update for scipy 0.8 and its changes to scipy.io 2010-06-07 version 1.1.3 - I don't remember the changes (I am a bad git user). 2010-04-19 updated to version 1.1.2 mainly getting rid of annoying error message 2010-03-31 Merge branch 'master' of github.com:ryanGT/controls - debugged leading zeros in numerators adding to Carten's patch - debugged TF.lsim and lsim2; set-up build_controls_py.py to make exe 2009-09-09 Small bug fixes 2009-07-04 Add ButterWorth filter Compensator Modified Paths: -------------- trunk/external/controls.py Modified: trunk/external/controls.py =================================================================== --- trunk/external/controls.py 2010-11-06 13:03:55 UTC (rev 29) +++ trunk/external/controls.py 2010-11-06 16:26:19 UTC (rev 30) @@ -11,10 +11,13 @@ from math import atan2, log10 from scipy import * +from scipy import signal +from scipy import interpolate, integrate from scipy.linalg import inv as inverse from scipy.optimize import newton, fmin, fminbound -from scipy.io import read_array, save, loadmat, write_array +#from scipy.io import read_array, save, loadmat, write_array from scipy import signal +from numpy.linalg import LinAlgError from IPython.Debugger import Pdb @@ -22,7 +25,7 @@ from matplotlib.ticker import LogFormatterMathtext -version = '1.1.0' +version = '1.1.4' class MyFormatter(LogFormatterMathtext): def __call__(self, x, pos=None): @@ -126,7 +129,10 @@ def prependzeros(num, den): nd = len(den) - nn = len(num) + if isscalar(num): + nn = 1 + else: + nn = len(num) if nn < nd: zvect = zeros(nd-nn) numout = r_[zvect, num] @@ -388,26 +394,42 @@ def __setattr__(self, attr, val): realizable = False if hasattr(self, 'den') and hasattr(self, 'num'): - realizable = _realizable(self.num, self.den) + realizable = _realizable(self.num, self.den) if realizable: - signal.lti.__setattr__(self, attr, val) + signal.lti.__setattr__(self, attr, val) else: - self.__dict__[attr] = val + self.__dict__[attr] = val - def __init__(self, num, den, dt=0.01, maxt=5.0, myvar='s'): + def __init__(self, num, den, dt=0.01, maxt=5.0, myvar='s', label='G'): """num and den are either scalar constants or lists that are passed to scipy.poly1d to create a list of coefficients.""" #print('in TransferFunction.__init__, dt=%s' % dt) if _realizable(num, den): - signal.lti.__init__(self, num, den) + num = atleast_1d(num) + den = atleast_1d(den) + start_num_ind = nonzero(num)[0][0] + start_den_ind = nonzero(den)[0][0] + num_ = num[start_num_ind:] + den_ = den[start_den_ind:] + signal.lti.__init__(self, num_, den_) + else: + z, p, k = signal.tf2zpk(num, den) + self.gain = k self.num = poly1d(num) self.den = poly1d(den) self.dt = dt self.myvar = myvar self.maxt = maxt + self.label = label + def print_poles(self, label=None): + if label is None: + label = self.label + print(label +' poles =' + str(self.poles)) + + def __repr__(self, labelstr='controls.TransferFunction'): nstr=str(self.num)#.strip() dstr=str(self.den)#.strip() @@ -502,6 +524,16 @@ return self.dt + def simplify(self, rtol=1e-5, atol=1e-10): + """Return a new TransferFunction object with poles and zeros + that nearly cancel (within real or absolutie tolerance rtol + and atol) removed.""" + gain = self.gain + new_num, new_den = polyfactor(self.num, self.den, prepend=False) + newtf = self.__class__(new_num*gain, new_den) + return newtf + + def ToLatex(self, eps=1e-12, fmt='%0.5g', ds=True): mynum = self.num myden = self.den @@ -590,7 +622,7 @@ def rlocfind(self, poleloc): self.poleloc = poleloc kinit,pinit = _k_poles(self,poleloc) - k = optimize.fmin(self.opt,[kinit])[0] + k = fmin(self.opt,[kinit])[0] poles = self._RLFindRoots([k]) poles = self._RLSortRoots(poles) return k, poles @@ -646,26 +678,102 @@ self.num = self.num/const self.den = self.den/const - def lsim(self, u, t, interp=0, returnall=False, X0=None): + def lsim(self, u, t, interp=0, returnall=False, X0=None, hmax=None): """Find the response of the TransferFunction to the input u with time vector t. Uses signal.lsim. return y the response of the system.""" + try: + out = signal.lsim(self, u, t, interp=interp, X0=X0) + except LinAlgError: + #if the system has a pure integrator, lsim won't work. + #Call lsim2. + out = self.lsim2(u, t, X0=X0, returnall=True, hmax=hmax) + #override returnall because it is handled below if returnall:#most users will just want the system output y, #but some will need the (t, y, x) tuple that #signal.lsim returns - return signal.lsim(self, u, t, interp=interp, X0=X0) + return out else: - return signal.lsim(self, u, t, interp=interp, X0=X0)[1] + return out[1] - def lsim2(self, u, t, returnall=False, X0=None): - #tempsys=signal.lti(self.num,self.den) - if returnall: - return signal.lsim2(self, u, t, X0=X0) - else: - return signal.lsim2(self, u, t, X0=X0)[1] +## def lsim2(self, u, t, returnall=False, X0=None): +## #tempsys=signal.lti(self.num,self.den) +## if returnall: +## return signal.lsim2(self, u, t, X0=X0) +## else: +## return signal.lsim2(self, u, t, X0=X0)[1] + def lsim2(self, U, T, X0=None, returnall=False, hmax=None): + """Simulate output of a continuous-time linear system, using ODE solver. + Inputs: + + system -- an instance of the LTI class or a tuple describing the + system. The following gives the number of elements in + the tuple and the interpretation. + 2 (num, den) + 3 (zeros, poles, gain) + 4 (A, B, C, D) + U -- an input array describing the input at each time T + (linear interpolation is assumed between given times). + If there are multiple inputs, then each column of the + rank-2 array represents an input. + T -- the time steps at which the input is defined and at which + the output is desired. + X0 -- (optional, default=0) the initial conditions on the state vector. + + Outputs: (T, yout, xout) + + T -- the time values for the output. + yout -- the response of the system. + xout -- the time-evolution of the state-vector. + """ + # system is an lti system or a sequence + # with 2 (num, den) + # 3 (zeros, poles, gain) + # 4 (A, B, C, D) + # describing the system + # U is an input vector at times T + # if system describes multiple outputs + # then U can be a rank-2 array with the number of columns + # being the number of inputs + + # rather than use lsim, use direct integration and matrix-exponential. + if hmax is None: + hmax = T[1]-T[0] + U = atleast_1d(U) + T = atleast_1d(T) + if len(U.shape) == 1: + U = U.reshape((U.shape[0],1)) + sU = U.shape + if len(T.shape) != 1: + raise ValueError, "T must be a rank-1 array." + if sU[0] != len(T): + raise ValueError, "U must have the same number of rows as elements in T." + if sU[1] != self.inputs: + raise ValueError, "System does not define that many inputs." + + if X0 is None: + X0 = zeros(self.B.shape[0],self.A.dtype) + + # for each output point directly integrate assume zero-order hold + # or linear interpolation. + + ufunc = interpolate.interp1d(T, U, kind='linear', axis=0, \ + bounds_error=False) + + def fprime(x, t, self, ufunc): + return dot(self.A,x) + squeeze(dot(self.B,nan_to_num(ufunc([t])))) + + xout = integrate.odeint(fprime, X0, T, args=(self, ufunc), hmax=hmax) + yout = dot(self.C,transpose(xout)) + dot(self.D,transpose(U)) + if returnall: + return T, squeeze(transpose(yout)), xout + else: + return squeeze(transpose(yout)) + + def residue(self, tol=1e-3, verbose=0): """from scipy.signal.residue: @@ -748,7 +856,8 @@ def FreqResp(self, f, fignum=1, fig=None, clear=True, \ - grid=True, legend=None, legloc=1, legsub=1, **kwargs): + grid=True, legend=None, legloc=1, legsub=1, \ + use_rad=False, **kwargs): """Compute the frequency response of the transfer function using the frequency vector f, returning a complex vector. @@ -765,10 +874,14 @@ if testvect.all(): s=f#then you really sent me s and not f else: - s=2.0j*pi*f + if use_rad: + s = 1.0j*f + else: + s=2.0j*pi*f self.comp = self.num(s)/self.den(s) self.dBmag = 20*log10(abs(self.comp)) - self.phase = angle(self.comp,1) + rphase = unwrap(angle(self.comp)) + self.phase = rphase*180.0/pi if fig is None: if fignum is not None: @@ -778,19 +891,19 @@ if fig is not None: if clear: fig.clf() + ax1 = fig.add_subplot(2,1,1) + ax2 = fig.add_subplot(2,1,2, sharex=ax1) + else: + ax1 = fig.axes[0] + ax2 = fig.axes[1] if fig is not None: - myargs=['linetype','colors','linewidth'] + myargs=['linetype','linewidth'] subkwargs={} for key in myargs: if kwargs.has_key(key): subkwargs[key]=kwargs[key] - if clear: - fig.clf() - ax1 = fig.add_subplot(2,1,1) - #if clear: - # ax1.cla() - myind=ax1._get_lines.count + #myind=ax1._get_lines.count mylines=_PlotMag(f, self, axis=ax1, **subkwargs) ax1.set_ylabel('Mag. Ratio (dB)') ax1.xaxis.set_major_formatter(MyFormatter()) @@ -798,12 +911,12 @@ ax1.grid(1) if legend is not None and legsub==1: ax1.legend(legend, legloc) - ax2 = fig.add_subplot(2,1,2, sharex=ax1) - #if clear: - # ax2.cla() mylines=_PlotPhase(f, self, axis=ax2, **subkwargs) ax2.set_ylabel('Phase (deg.)') - ax2.set_xlabel('Freq. (Hz)') + if use_rad: + ax2.set_xlabel('$\\omega$ (rad./sec.)') + else: + ax2.set_xlabel('Freq. (Hz)') ax2.xaxis.set_major_formatter(MyFormatter()) if grid: ax2.grid(1) @@ -1142,8 +1255,8 @@ yvect = shift(yvect, curY) return Ydig*dv +TF = TransferFunction - class Input(TransferFunction): def __repr__(self): return TransferFunction.__repr__(self, labelstr='controls.Input') @@ -1259,8 +1372,17 @@ else: return vin +class ButterworthFilter(Compensator): + def __init__(self,fc,mag=1.0): + """Create a compensator that is a second order Butterworth + filter. fc is the corner frequency in Hz and mag is the low + frequency magnitude so that the transfer function will be + mag*wn**2/(s**2+2*z*wn*s+wn**2) where z=1/sqrt(2) and + wn=2.0*pi*fc.""" + z=1.0/sqrt(2.0) + wn=2.0*pi*fc + Compensator.__init__(self,mag*wn**2,[1.0,2.0*z*wn,wn**2]) - class Closed_Loop_System_with_Sat(object): def __init__(self, plant_tf, Kp, sat): self.plant_tf = plant_tf This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |