blob: 57268a90138d625feaeaf2f57fbab94998d4e9e1 [file] [log] [blame]
brians343bc112013-02-10 01:53:46 +00001#!/usr/bin/python
2
3"""
4Control loop pole placement library.
5
6This library will grow to support many different pole placement methods.
7Currently it only supports direct pole placement.
8"""
9
10__author__ = 'Austin Schuh (austin.linux@gmail.com)'
11
12import numpy
13import slycot
14
15class Error (Exception):
16 """Base class for all control loop exceptions."""
17
18
19class PolePlacementError(Error):
20 """Exception raised when pole placement fails."""
21
22
23# TODO(aschuh): dplace should take a control system object.
24# There should also exist a function to manipulate laplace expressions, and
25# something to plot bode plots and all that.
26def dplace(A, B, poles, alpha=1e-6):
27 """Set the poles of (A - BF) to poles.
28
29 Args:
30 A: numpy.matrix(n x n), The A matrix.
31 B: numpy.matrix(n x m), The B matrix.
32 poles: array(imaginary numbers), The poles to use. Complex conjugates poles
33 must be in pairs.
34
35 Raises:
36 ValueError: Arguments were the wrong shape or there were too many poles.
37 PolePlacementError: Pole placement failed.
38
39 Returns:
40 numpy.matrix(m x n), K
41 """
42 # See http://www.icm.tu-bs.de/NICONET/doc/SB01BD.html for a description of the
43 # fortran code that this is cleaning up the interface to.
44 n = A.shape[0]
45 if A.shape[1] != n:
46 raise ValueError("A must be square")
47 if B.shape[0] != n:
48 raise ValueError("B must have the same number of states as A.")
49 m = B.shape[1]
50
51 num_poles = len(poles)
52 if num_poles > n:
53 raise ValueError("Trying to place more poles than states.")
54
55 out = slycot.sb01bd(n=n,
56 m=m,
57 np=num_poles,
58 alpha=alpha,
59 A=A,
60 B=B,
61 w=numpy.array(poles),
62 dico='D')
63
64 A_z = numpy.matrix(out[0])
65 num_too_small_eigenvalues = out[2]
66 num_assigned_eigenvalues = out[3]
67 num_uncontrollable_eigenvalues = out[4]
68 K = numpy.matrix(-out[5])
69 Z = numpy.matrix(out[6])
70
71 if num_too_small_eigenvalues != 0:
72 raise PolePlacementError("Number of eigenvalues that are too small "
73 "and are therefore unmodified is %d." %
74 num_too_small_eigenvalues)
75 if num_assigned_eigenvalues != num_poles:
76 raise PolePlacementError("Did not place all the eigenvalues that were "
77 "requested. Only placed %d eigenvalues." %
78 num_assigned_eigenvalues)
79 if num_uncontrollable_eigenvalues != 0:
80 raise PolePlacementError("Found %d uncontrollable eigenvlaues." %
81 num_uncontrollable_eigenvalues)
82
83 return K
Austin Schuhc8ca2442013-02-23 12:29:33 -080084
85
86def c2d(A, B, dt):
87 """Converts from continuous time state space representation to discrete time.
Austin Schuhd34569d2014-02-18 20:26:38 -080088 Evaluates e^(A dt) - I for the discrete time version of A, and
Austin Schuhc8ca2442013-02-23 12:29:33 -080089 integral(e^(A t) * B, 0, dt).
90 Returns (A, B). C and D are unchanged."""
91 e, P = numpy.linalg.eig(A)
Austin Schuh1a387962015-01-31 16:36:20 -080092 diag = numpy.matrix(numpy.eye(A.shape[0]), dtype=numpy.complex128)
93 diage = numpy.matrix(numpy.eye(A.shape[0]), dtype=numpy.complex128)
Austin Schuhc8ca2442013-02-23 12:29:33 -080094 for eig, count in zip(e, range(0, A.shape[0])):
95 diag[count, count] = numpy.exp(eig * dt)
96 if abs(eig) < 1.0e-16:
97 diage[count, count] = dt
98 else:
99 diage[count, count] = (numpy.exp(eig * dt) - 1.0) / eig
100
Austin Schuh1a387962015-01-31 16:36:20 -0800101 ans_a = P * diag * numpy.linalg.inv(P)
102 ans_b = P * diage * numpy.linalg.inv(P) * B
103 if numpy.abs(ans_a.imag).sum() / numpy.abs(ans_a).sum() < 1e-6:
104 ans_a = ans_a.real
105 if numpy.abs(ans_b.imag).sum() / numpy.abs(ans_b).sum() < 1e-6:
106 ans_b = ans_b.real
107 return (ans_a, ans_b)
Austin Schuh7ec34fd2014-02-15 22:27:46 -0800108
109def ctrb(A, B):
110 """Returns the controlability matrix.
111
112 This matrix must have full rank for all the states to be controlable.
113 """
114 n = A.shape[0]
115 output = B
116 intermediate = B
117 for i in xrange(0, n):
118 intermediate = A * intermediate
119 output = numpy.concatenate((output, intermediate), axis=1)
120
121 return output
122
123def dlqr(A, B, Q, R):
124 """Solves for the optimal lqr controller.
125
126 x(n+1) = A * x(n) + B * u(n)
127 J = sum(0, inf, x.T * Q * x + u.T * R * u)
128 """
129
130 # P = (A.T * P * A) - (A.T * P * B * numpy.linalg.inv(R + B.T * P *B) * (A.T * P.T * B).T + Q
131
Austin Schuh1a387962015-01-31 16:36:20 -0800132 P, rcond, w, S, T = slycot.sb02od(
133 n=A.shape[0], m=B.shape[1], A=A, B=B, Q=Q, R=R, dico='D')
Austin Schuh7ec34fd2014-02-15 22:27:46 -0800134
135 F = numpy.linalg.inv(R + B.T * P *B) * B.T * P * A
136 return F