├── .gitignore
├── README.md
└── src
├── ClosedLoop.gif
├── ClosedLoopStates.gif
├── ClosedLoop_multiLap.gif
├── fnc
├── Utilities.py
├── controller
│ ├── PredictiveControllers.py
│ └── PredictiveModel.py
├── plot.py
└── simulator
│ ├── SysModel.py
│ └── Track.py
├── initControllerParameters.py
└── main.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.xml
2 | *.pyc
3 | *.obj
4 | **/*.obj
5 | .DS_Store
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Learning Model Predictive Control (LMPC) for autonomous racing
2 |
3 | The Learning Model Predictive Control (LMPC) is a data-driven control framework developed at UCB in the MPC lab. In this example, we implemented the LMPC for the autonomous racing problem. The controller drives several laps on race track and it learns from experience how to drive faster.
4 |
5 |
6 |
7 |
8 |
9 |
10 | In the above animation we see the vehicle's closed-loop trajectory (in black) for laps 5, 30, 31 and 32. At each time instant the LMPC leverages forecast to plan the vehicle trajectory (in red) few seconds into the future. This trajectory is planned to minimize the lap time, but it is constrained to land into the safe set (in green). This safe set is the domain of the approximation to the value function and it is updated after each lap using historical data.
11 |
12 | ### Prerequisites
13 |
14 | The packeges needed for running the code can be installed using pip
15 |
16 | ```
17 | pip install cvxopt
18 | pip install osqp
19 | pip install pathos
20 | ```
21 |
22 | ## Description
23 |
24 | ### The Plant
25 | The vehicle is modelled using the dynamics signle track bicycle model and the tire forces are modelled using the Pacejka formula.
26 |
27 | ### The Path Following
28 | 1) Lap 1: a PID path following controller is used to drive the vehicle around the track.
29 | 2) Lap 2: the data from lap 1 are used to estimate a LTI model used to design a MPC for path following
30 | 3) Lap 3: the data from lap 1 are used to estimate a LTV model used to design a MPC for path following
31 |
32 |
33 | ## References
34 |
35 | This code is based on the following:
36 |
37 | * Ugo Rosolia and Francesco Borrelli. "Learning Model Predictive Control for Iterative Tasks. A Data-Driven Control Framework." In IEEE Transactions on Automatic Control (2017). [PDF](https://ieeexplore.ieee.org/document/8039204/)
38 | * Ugo Rosolia and Francesco Borrelli. "Learning how to autonomously race a car: a predictive control approach." IEEE Transactions on Control Systems Technology (2019) [PDF](https://ieeexplore.ieee.org/abstract/document/8896988).
39 | * Ugo Rosolia and Francesco Borrelli. "Learning Model Predictive Control for Iterative Tasks: A Computationally Efficient Approach for Linear System." IFAC-PapersOnLine 50.1 (2017). [PDF](https://arxiv.org/pdf/1702.07064.pdf)
40 |
--------------------------------------------------------------------------------
/src/ClosedLoop.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/urosolia/RacingLMPC/b49c95e7795ef6f343efacedb6b90a69c7855b32/src/ClosedLoop.gif
--------------------------------------------------------------------------------
/src/ClosedLoopStates.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/urosolia/RacingLMPC/b49c95e7795ef6f343efacedb6b90a69c7855b32/src/ClosedLoopStates.gif
--------------------------------------------------------------------------------
/src/ClosedLoop_multiLap.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/urosolia/RacingLMPC/b49c95e7795ef6f343efacedb6b90a69c7855b32/src/ClosedLoop_multiLap.gif
--------------------------------------------------------------------------------
/src/fnc/Utilities.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import pdb
3 | import datetime
4 |
5 | def Regression(x, u, lamb):
6 | """Estimates linear system dynamics
7 | x, u: date used in the regression
8 | lamb: regularization coefficient
9 | """
10 |
11 | # Want to solve W^* = argmin sum_i ||W^T z_i - y_i ||_2^2 + lamb ||W||_F,
12 | # with z_i = [x_i u_i] and W \in R^{n + d} x n
13 | Y = x[2:x.shape[0], :]
14 | X = np.hstack((x[1:(x.shape[0] - 1), :], u[1:(x.shape[0] - 1), :]))
15 |
16 | Q = np.linalg.inv(np.dot(X.T, X) + lamb * np.eye(X.shape[1]))
17 | b = np.dot(X.T, Y)
18 | W = np.dot(Q, b)
19 |
20 | A = W.T[:, 0:6]
21 | B = W.T[:, 6:8]
22 |
23 | ErrorMatrix = np.dot(X, W) - Y
24 | ErrorMax = np.max(ErrorMatrix, axis=0)
25 | ErrorMin = np.min(ErrorMatrix, axis=0)
26 | Error = np.vstack((ErrorMax, ErrorMin))
27 |
28 | return A, B, Error
29 |
30 |
31 | def wrap(angle):
32 | if angle < -np.pi:
33 | w_angle = 2 * np.pi + angle
34 | elif angle > np.pi:
35 | w_angle = angle - 2 * np.pi
36 | else:
37 | w_angle = angle
38 |
39 | return w_angle
40 |
41 |
42 | class PID:
43 | """Create the PID controller used for path following at constant speed
44 | Attributes:
45 | solve: given x0 computes the control action
46 | """
47 | def __init__(self, vt):
48 | """Initialization
49 | Arguments:
50 | vt: target velocity
51 | """
52 | self.vt = vt
53 | self.uPred = np.zeros([1,2])
54 |
55 | startTimer = datetime.datetime.now()
56 | endTimer = datetime.datetime.now(); deltaTimer = endTimer - startTimer
57 | self.solverTime = deltaTimer
58 | self.linearizationTime = deltaTimer
59 | self.feasible = 1
60 |
61 | def solve(self, x0):
62 | """Computes control action
63 | Arguments:
64 | x0: current state position
65 | """
66 | vt = self.vt
67 | self.uPred[0, 0] = - 0.6 * x0[5] - 0.9 * x0[3] + np.max([-0.9, np.min([np.random.randn() * 0.25, 0.9])])
68 | self.uPred[0, 1] = 1.5 * (vt - x0[0]) + np.max([-0.2, np.min([np.random.randn() * 0.10, 0.2])])
--------------------------------------------------------------------------------
/src/fnc/controller/PredictiveControllers.py:
--------------------------------------------------------------------------------
1 | import pdb
2 | import numpy as np
3 | from cvxopt import spmatrix, matrix, solvers
4 | from numpy import linalg as la
5 | from scipy import linalg
6 | from scipy import sparse
7 | from cvxopt.solvers import qp
8 | import datetime
9 | from numpy import hstack, inf, ones
10 | from scipy.sparse import vstack
11 | from osqp import OSQP
12 | from dataclasses import dataclass, field
13 |
14 | solvers.options['show_progress'] = False
15 |
16 | @dataclass
17 | class PythonMsg:
18 | def __setattr__(self,key,value):
19 | if not hasattr(self,key):
20 | raise TypeError ('Cannot add new field "%s" to frozen class %s' %(key,self))
21 | else:
22 | object.__setattr__(self,key,value)
23 |
24 | @dataclass
25 | class MPCParams(PythonMsg):
26 | n: int = field(default=None) # dimension state space
27 | d: int = field(default=None) # dimension input space
28 | N: int = field(default=None) # horizon length
29 |
30 | A: np.array = field(default=None) # prediction matrices. Single matrix for LTI and list for LTV
31 | B: np.array = field(default=None) # prediction matrices. Single matrix for LTI and list for LTV
32 |
33 | Q: np.array = field(default=np.array((n, n))) # quadratic state cost
34 | R: np.array = field(default=None) # quadratic input cost
35 | Qf: np.array = field(default=None) # quadratic state cost final
36 | dR: np.array = field(default=None) # Quadratic rate cost
37 |
38 | Qslack: float = field(default=None) # it has to be a vector. Qslack = [linearSlackCost, quadraticSlackCost]
39 | Fx: np.array = field(default=None) # State constraint Fx * x <= bx
40 | bx: np.array = field(default=None)
41 | Fu: np.array = field(default=None) # State constraint Fu * u <= bu
42 | bu: np.array = field(default=None)
43 | xRef: np.array = field(default=None)
44 |
45 | slacks: bool = field(default=True)
46 | timeVarying: bool = field(default=False)
47 |
48 | def __post_init__(self):
49 | if self.Qf is None: self.Qf = np.zeros((self.n, self.n))
50 | if self.dR is None: self.dR = np.zeros(self.d)
51 | if self.xRef is None: self.xRef = np.zeros(self.n)
52 |
53 | ############################################################################################
54 | ####################################### MPC CLASS ##########################################
55 | ############################################################################################
56 | class MPC():
57 | """Model Predicitve Controller class
58 | Methods (needed by user):
59 | solve: given system's state xt compute control action at
60 | Arguments:
61 | mpcParameters: model paramters
62 | """
63 | def __init__(self, mpcParameters, predictiveModel=[]):
64 | """Initialization
65 | Arguments:
66 | mpcParameters: struct containing MPC parameters
67 | """
68 | self.N = mpcParameters.N
69 | self.Qslack = mpcParameters.Qslack
70 | self.Q = mpcParameters.Q
71 | self.Qf = mpcParameters.Qf
72 | self.R = mpcParameters.R
73 | self.dR = mpcParameters.dR
74 | self.n = mpcParameters.n
75 | self.d = mpcParameters.d
76 | self.A = mpcParameters.A
77 | self.B = mpcParameters.B
78 | self.Fx = mpcParameters.Fx
79 | self.Fu = mpcParameters.Fu
80 | self.bx = mpcParameters.bx
81 | self.bu = mpcParameters.bu
82 | self.xRef = mpcParameters.xRef
83 |
84 | self.slacks = mpcParameters.slacks
85 | self.timeVarying = mpcParameters.timeVarying
86 | self.predictiveModel = predictiveModel
87 |
88 | if self.timeVarying == True:
89 | self.xLin = self.predictiveModel.xStored[-1][0:self.N+1,:]
90 | self.uLin = self.predictiveModel.uStored[-1][0:self.N,:]
91 | self.computeLTVdynamics()
92 |
93 | self.OldInput = np.zeros((1,2)) # TO DO fix size
94 |
95 | # Build matrices for inequality constraints
96 | self.buildIneqConstr()
97 | self.buildCost()
98 | self.buildEqConstr()
99 |
100 | self.xPred = []
101 |
102 | # initialize time
103 | startTimer = datetime.datetime.now()
104 | endTimer = datetime.datetime.now(); deltaTimer = endTimer - startTimer
105 | self.solverTime = deltaTimer
106 | self.linearizationTime = deltaTimer
107 | self.timeStep = 0
108 |
109 |
110 | def solve(self, x0):
111 | """Computes control action
112 | Arguments:
113 | x0: current state
114 | """
115 | # If LTV active --> identify system model
116 | if self.timeVarying == True:
117 | self.computeLTVdynamics()
118 | self.buildCost()
119 | self.buildEqConstr()
120 |
121 | self.addTerminalComponents(x0)
122 | # Solve QP
123 | startTimer = datetime.datetime.now()
124 | self.osqp_solve_qp(self.H_FTOCP, self.q_FTOCP, self.F_FTOCP, self.b_FTOCP, self.G_FTOCP, np.add(np.dot(self.E_FTOCP,x0),self.L_FTOCP))
125 | self.unpackSolution()
126 | endTimer = datetime.datetime.now(); deltaTimer = endTimer - startTimer
127 | self.solverTime = deltaTimer
128 |
129 | # If LTV active --> compute state-input linearization trajectory
130 | self.feasibleStateInput()
131 | if self.timeVarying == True:
132 | self.xLin = np.vstack((self.xPred[1:, :], self.zt))
133 | self.uLin = np.vstack((self.uPred[1:, :], self.zt_u))
134 |
135 | # update applied input
136 | self.OldInput = self.uPred[0,:]
137 | self.timeStep += 1
138 |
139 |
140 | def computeLTVdynamics(self):
141 | # Estimate system dynamics
142 | self.A = []; self.B = []; self.C =[]
143 | for i in range(0, self.N):
144 | Ai, Bi, Ci = self.predictiveModel.regressionAndLinearization(self.xLin[i], self.uLin[i])
145 | self.A.append(Ai); self.B.append(Bi); self.C.append(Ci)
146 |
147 | def addTerminalComponents(self, x0):
148 | # TO DO: ....
149 | self.H_FTOCP = sparse.csc_matrix(self.H)
150 | self.q_FTOCP = self.q
151 | self.F_FTOCP = sparse.csc_matrix(self.F)
152 | self.b_FTOCP = self.b
153 | self.G_FTOCP = sparse.csc_matrix(self.G)
154 | self.E_FTOCP = self.E
155 | self.L_FTOCP = self.L
156 |
157 | def feasibleStateInput(self):
158 | self.zt = self.xPred[-1,:]
159 | self.zt_u = self.uPred[-1,:]
160 |
161 | def unpackSolution(self):
162 | # Extract predicted state and predicted input trajectories
163 | self.xPred = np.squeeze(np.transpose(np.reshape((self.Solution[np.arange(self.n*(self.N+1))]),(self.N+1,self.n)))).T
164 | self.uPred = np.squeeze(np.transpose(np.reshape((self.Solution[self.n*(self.N+1)+np.arange(self.d*self.N)]),(self.N, self.d)))).T
165 |
166 | def buildIneqConstr(self):
167 | # The inequality constraint is Fz<=b
168 | # Let's start by computing the submatrix of F relates with the state
169 | rep_a = [self.Fx] * (self.N)
170 | Mat = linalg.block_diag(*rep_a)
171 | NoTerminalConstr = np.zeros((np.shape(Mat)[0], self.n)) # The last state is unconstrained. There is a specific function add the terminal constraints (so that more complicated terminal constrains can be handled)
172 | Fxtot = np.hstack((Mat, NoTerminalConstr))
173 | bxtot = np.tile(np.squeeze(self.bx), self.N)
174 |
175 | # Let's start by computing the submatrix of F relates with the input
176 | rep_b = [self.Fu] * (self.N)
177 | Futot = linalg.block_diag(*rep_b)
178 | butot = np.tile(np.squeeze(self.bu), self.N)
179 |
180 | # Let's stack all together
181 | F_hard = linalg.block_diag(Fxtot, Futot)
182 |
183 | # Add slack if need
184 | if self.slacks == True:
185 | nc_x = self.Fx.shape[0] # add slack only for state constraints
186 | # Fist add add slack to existing constraints
187 | addSlack = np.zeros((F_hard.shape[0], nc_x*self.N))
188 | addSlack[0:nc_x*(self.N), 0:nc_x*(self.N)] = -np.eye(nc_x*(self.N))
189 | # Now constraint slacks >= 0
190 | I = - np.eye(nc_x*self.N); Zeros = np.zeros((nc_x*self.N, F_hard.shape[1]))
191 | Positivity = np.hstack((Zeros, I))
192 |
193 | # Let's stack all together
194 | self.F = np.vstack(( np.hstack((F_hard, addSlack)) , Positivity))
195 | self.b = np.hstack((bxtot, butot, np.zeros(nc_x*self.N)))
196 | else:
197 | self.F = F_hard
198 | self.b = np.hstack((bxtot, butot))
199 |
200 | def buildEqConstr(self):
201 | # Buil matrices for optimization (Convention from Chapter 15.2 Borrelli, Bemporad and Morari MPC book)
202 | # The equality constraint is: G*z = E * x(t) + L
203 | Gx = np.eye(self.n * (self.N + 1))
204 | Gu = np.zeros((self.n * (self.N + 1), self.d * (self.N)))
205 |
206 | E = np.zeros((self.n * (self.N + 1), self.n))
207 | E[np.arange(self.n)] = np.eye(self.n)
208 |
209 | L = np.zeros(self.n * (self.N + 1))
210 |
211 | for i in range(0, self.N):
212 | if self.timeVarying == True:
213 | Gx[(self.n + i*self.n):(self.n + i*self.n + self.n), (i*self.n):(i*self.n + self.n)] = -self.A[i]
214 | Gu[(self.n + i*self.n):(self.n + i*self.n + self.n), (i*self.d):(i*self.d + self.d)] = -self.B[i]
215 | L[(self.n + i*self.n):(self.n + i*self.n + self.n)] = self.C[i]
216 | else:
217 | Gx[(self.n + i*self.n):(self.n + i*self.n + self.n), (i*self.n):(i*self.n + self.n)] = -self.A
218 | Gu[(self.n + i*self.n):(self.n + i*self.n + self.n), (i*self.d):(i*self.d + self.d)] = -self.B
219 |
220 | if self.slacks == True:
221 | self.G = np.hstack( (Gx, Gu, np.zeros( ( Gx.shape[0], self.Fx.shape[0]*self.N) ) ) )
222 | else:
223 | self.G = np.hstack((Gx, Gu))
224 |
225 | self.E = E
226 | self.L = L
227 |
228 | def buildCost(self):
229 | # The cost is: (1/2) * z' H z + q' z
230 | listQ = [self.Q] * (self.N)
231 | Hx = linalg.block_diag(*listQ)
232 |
233 | listTotR = [self.R + 2 * np.diag(self.dR)] * (self.N) # Need to add dR for the derivative input cost
234 | Hu = linalg.block_diag(*listTotR)
235 | # Need to condider that the last input appears just once in the difference
236 | for i in range(0, self.d):
237 | Hu[ i - self.d, i - self.d] = Hu[ i - self.d, i - self.d] - self.dR[i]
238 |
239 | # Derivative Input Cost
240 | OffDiaf = -np.tile(self.dR, self.N-1)
241 | np.fill_diagonal(Hu[self.d:], OffDiaf)
242 | np.fill_diagonal(Hu[:, self.d:], OffDiaf)
243 |
244 | # Cost linear term for state and input
245 | q = - 2 * np.dot(np.append(np.tile(self.xRef, self.N + 1), np.zeros(self.R.shape[0] * self.N)), linalg.block_diag(Hx, self.Qf, Hu))
246 | # Derivative Input (need to consider input at previous time step)
247 | q[self.n*(self.N+1):self.n*(self.N+1)+self.d] = -2 * np.dot( self.OldInput, np.diag(self.dR) )
248 | if self.slacks == True:
249 | quadSlack = self.Qslack[0] * np.eye(self.Fx.shape[0]*self.N)
250 | linSlack = self.Qslack[1] * np.ones(self.Fx.shape[0]*self.N )
251 | self.H = linalg.block_diag(Hx, self.Qf, Hu, quadSlack)
252 | self.q = np.append(q, linSlack)
253 | else:
254 | self.H = linalg.block_diag(Hx, self.Qf, Hu)
255 | self.q = q
256 |
257 | self.H = 2 * self.H # Need to multiply by two because CVX considers 1/2 in front of quadratic cost
258 |
259 | def osqp_solve_qp(self, P, q, G= None, h=None, A=None, b=None, initvals=None):
260 | """
261 | Solve a Quadratic Program defined as:
262 | minimize
263 | (1/2) * x.T * P * x + q.T * x
264 | subject to
265 | G * x <= h
266 | A * x == b
267 | using OSQP .
268 | """
269 | self.osqp = OSQP()
270 | qp_A = vstack([G, A]).tocsc()
271 | l = -inf * ones(len(h))
272 | qp_l = hstack([l, b])
273 | qp_u = hstack([h, b])
274 |
275 | self.osqp.setup(P=P, q=q, A=qp_A, l=qp_l, u=qp_u, verbose=False, polish=True)
276 | if initvals is not None:
277 | self.osqp.warm_start(x=initvals)
278 | res = self.osqp.solve()
279 | if res.info.status_val == 1:
280 | self.feasible = 1
281 | else:
282 | self.feasible = 0
283 | self.Solution = res.x
284 |
285 | ############## Below LMPC class which is a child of the MPC super class
286 | class LMPC(MPC):
287 | """Create the LMPC
288 | Methods (needed by user):
289 | solve: given x0 computes the control action
290 | addTrajectory: given a ClosedLoopData object adds the trajectory to SS, Qfun, uSS and updates the iteration index
291 | addPoint: this function allows to add the closed loop data at iteration j to the SS of iteration (j-1)
292 | """
293 | def __init__(self, numSS_Points, numSS_it, QterminalSlack, mpcPrameters, predictiveModel, dt = 0.1):
294 | """Initialization
295 | Arguments:
296 | numSS_Points: number of points selected from the previous trajectories to build SS
297 | numSS_it: number of previois trajectories selected to build SS
298 | N: horizon length
299 | Q,R: weight to define cost function h(x,u) = ||x||_Q + ||u||_R
300 | dR: weight to define the input rate cost h(x,u) = ||x_{k+1}-x_k||_dR
301 | n,d: state and input dimensiton
302 | shift: given the closest point x_t^j to x(t) the controller start selecting the point for SS from x_{t+shift}^j
303 | map: map
304 | Laps: maximum number of laps the controller can run (used to avoid dynamic allocation)
305 | TimeLMPC: maximum time [s] that an lap can last (used to avoid dynamic allocation)
306 | Solver: solver used in the reformulation of the LMPC as QP
307 | """
308 | super().__init__(mpcPrameters, predictiveModel)
309 | self.numSS_Points = numSS_Points
310 | self.numSS_it = numSS_it
311 | self.QterminalSlack = QterminalSlack
312 |
313 | self.OldInput = np.zeros((1,2))
314 | self.xPred = []
315 |
316 | # Initialize the following quantities to avoid dynamic allocation
317 | self.LapTime = [] # Time at which each j-th iteration is completed
318 | self.SS = [] # Sampled Safe SS
319 | self.uSS = [] # Input associated with the points in SS
320 | self.Qfun = [] # Qfun: cost-to-go from each point in SS
321 | self.SS_glob = [] # SS in global (X-Y) used for plotting
322 |
323 | self.xStoredPredTraj = []
324 | self.xStoredPredTraj_it = []
325 | self.uStoredPredTraj = []
326 | self.uStoredPredTraj_it = []
327 | self.SSStoredPredTraj = []
328 | self.SSStoredPredTraj_it = []
329 |
330 | self.zt = np.array([0.0, 0.0, 0.0, 0.0, 10.0, 0.0])
331 |
332 | # Initialize the controller iteration
333 | self.it = 0
334 |
335 | # Build matrices for inequality constraints
336 | self.buildIneqConstr()
337 | self.buildCost()
338 | self.addSafeSetIneqConstr()
339 |
340 | def addSafeSetIneqConstr(self):
341 | # Add positiviti constraints for lambda_{SafeSet}. Note that no constraint is enforced on slack_{SafeSet} ---> add np.hstack(-np.eye(self.numSS_Points), np.zeros(self.n))
342 | self.F_FTOCP = sparse.csc_matrix( linalg.block_diag( self.F, np.hstack((-np.eye(self.numSS_Points), np.zeros((self.numSS_Points, self.n)))) ) )
343 | self.b_FTOCP = np.append(self.b, np.zeros(self.numSS_Points))
344 |
345 | def addSafeSetEqConstr(self):
346 | # Add constrains for x, u, slack
347 | xTermCons = np.zeros((self.n, self.G.shape[1]))
348 | xTermCons[:, self.N * self.n:(self.N + 1) * self.n] = np.eye(self.n)
349 | G_x_u_slack = np.vstack((self.G, xTermCons))
350 | # Constraint for lambda_{SaFeSet, slack_{safeset}} to enforce safe set
351 | G_lambda_slackSafeSet = np.vstack( (np.zeros((self.G.shape[0], self.SS_PointSelectedTot.shape[1] + self.n)), np.hstack((-self.SS_PointSelectedTot, np.eye(self.n)))) )
352 | # Constraints on lambda = 1
353 | G_lambda = np.append(np.append(np.zeros(self.G.shape[1]), np.ones(self.SS_PointSelectedTot.shape[1])), np.zeros(self.n))
354 | # Put all together
355 | self.G_FTOCP = sparse.csc_matrix(np.vstack((np.hstack((G_x_u_slack, G_lambda_slackSafeSet)), G_lambda)))
356 | self.E_FTOCP = np.vstack((self.E, np.zeros((self.n+1,self.n)))) # adding n for terminal constraint and 1 for lambda = 1
357 | self.L_FTOCP = np.append(np.append(self.L, np.zeros(self.n)), 1)
358 |
359 | def addSafeSetCost(self):
360 | # need to multiply the quadratic term as cost is (1/2) z'*Q*z
361 | self.H_FTOCP = sparse.csc_matrix(linalg.block_diag(self.H, np.zeros((self.SS_PointSelectedTot.shape[1], self.SS_PointSelectedTot.shape[1])), 2*self.QterminalSlack) )
362 | self.q_FTOCP = np.append(np.append(self.q, self.Qfun_SelectedTot), np.zeros(self.n))
363 |
364 | def unpackSolution(self):
365 | stateIdx = self.n*(self.N+1)
366 | inputIdx = stateIdx + self.d*self.N
367 | slackIdx = inputIdx + self.Fx.shape[0]*self.N
368 | lambdIdx = slackIdx + self.SS_PointSelectedTot.shape[1]
369 | sTermIdx = lambdIdx + self.n
370 |
371 | self.xPred = np.squeeze(np.transpose(np.reshape((self.Solution[np.arange(self.n*(self.N+1))]),(self.N+1,self.n)))).T
372 | self.uPred = np.squeeze(np.transpose(np.reshape((self.Solution[self.n*(self.N+1)+np.arange(self.d*self.N)]),(self.N, self.d)))).T
373 | self.slack = self.Solution[inputIdx:slackIdx]
374 | self.lambd = self.Solution[slackIdx:lambdIdx]
375 | self.slackTerminal = self.Solution[lambdIdx:]
376 |
377 | self.xStoredPredTraj_it.append(self.xPred)
378 | self.uStoredPredTraj_it.append(self.uPred)
379 | self.SSStoredPredTraj_it.append(self.SS_PointSelectedTot.T)
380 |
381 |
382 | def feasibleStateInput(self):
383 | self.zt = np.dot(self.Succ_SS_PointSelectedTot, self.lambd)
384 | self.zt_u = np.dot(self.Succ_uSS_PointSelectedTot, self.lambd)
385 |
386 | def addTerminalComponents(self,x0):
387 | """add terminal constraint and terminal cost
388 | Arguments:
389 | x: initial condition
390 | """
391 | # Update zt and xLin is they have crossed the finish line. We want s \in [0, TrackLength]
392 | if (self.zt[4]-x0[4] > self.predictiveModel.map.TrackLength/2):
393 | self.zt[4] = np.max([self.zt[4] - self.predictiveModel.map.TrackLength,0])
394 | self.xLin[4,-1] = self.xLin[4,-1]- self.predictiveModel.map.TrackLength
395 | sortedLapTime = np.argsort(np.array(self.LapTime))
396 |
397 | # Select Points from historical data. These points will be used to construct the terminal cost function and constraint set
398 | SS_PointSelectedTot = np.empty((self.n, 0))
399 | Succ_SS_PointSelectedTot = np.empty((self.n, 0))
400 | Succ_uSS_PointSelectedTot = np.empty((self.d, 0))
401 | Qfun_SelectedTot = np.empty((0))
402 | for jj in sortedLapTime[0:self.numSS_it]:
403 | SS_PointSelected, uSS_PointSelected, Qfun_Selected = self.selectPoints(jj, self.zt, self.numSS_Points / self.numSS_it + 1)
404 | Succ_SS_PointSelectedTot = np.append(Succ_SS_PointSelectedTot, SS_PointSelected[:,1:], axis=1)
405 | Succ_uSS_PointSelectedTot = np.append(Succ_uSS_PointSelectedTot, uSS_PointSelected[:,1:], axis=1)
406 | SS_PointSelectedTot = np.append(SS_PointSelectedTot, SS_PointSelected[:,0:-1], axis=1)
407 | Qfun_SelectedTot = np.append(Qfun_SelectedTot, Qfun_Selected[0:-1], axis=0)
408 |
409 | self.Succ_SS_PointSelectedTot = Succ_SS_PointSelectedTot
410 | self.Succ_uSS_PointSelectedTot = Succ_uSS_PointSelectedTot
411 | self.SS_PointSelectedTot = SS_PointSelectedTot
412 | self.Qfun_SelectedTot = Qfun_SelectedTot
413 |
414 | # Update terminal set and cost
415 | self.addSafeSetEqConstr()
416 | self.addSafeSetCost()
417 |
418 | def addTrajectory(self, x, u, x_glob):
419 | """update iteration index and construct SS, uSS and Qfun
420 | Arguments:
421 | x: closed-loop trajectory
422 | u: applied inputs
423 | x_gloab: closed-loop trajectory in global frame
424 | """
425 | self.LapTime.append(x.shape[0])
426 | self.SS.append(x)
427 | self.SS_glob.append(x_glob)
428 | self.uSS.append(u)
429 | self.Qfun.append(self.computeCost(x,u))
430 |
431 | if self.it == 0:
432 | self.xLin = self.SS[self.it][1:self.N + 2, :]
433 | self.uLin = self.uSS[self.it][1:self.N + 1, :]
434 |
435 | self.xStoredPredTraj.append(self.xStoredPredTraj_it)
436 | self.xStoredPredTraj_it = []
437 |
438 | self.uStoredPredTraj.append(self.uStoredPredTraj_it)
439 | self.uStoredPredTraj_it = []
440 |
441 | self.SSStoredPredTraj.append(self.SSStoredPredTraj_it)
442 | self.SSStoredPredTraj_it = []
443 |
444 | self.it = self.it + 1
445 | self.timeStep = 0
446 |
447 | def computeCost(self, x, u):
448 | """compute roll-out cost
449 | Arguments:
450 | x: closed-loop trajectory
451 | u: applied inputs
452 | """
453 | Cost = 10000 * np.ones((x.shape[0])) # The cost has the same elements of the vector x --> time +1
454 | # Now compute the cost moving backwards in a Dynamic Programming (DP) fashion.
455 | # We start from the last element of the vector x and we sum the running cost
456 | for i in range(0, x.shape[0]):
457 | if (i == 0): # Note that for i = 0 --> pick the latest element of the vector x
458 | Cost[x.shape[0] - 1 - i] = 0
459 | elif x[x.shape[0] - 1 - i, 4]< self.predictiveModel.map.TrackLength:
460 | Cost[x.shape[0] - 1 - i] = Cost[x.shape[0] - 1 - i + 1] + 1
461 | else:
462 | Cost[x.shape[0] - 1 - i] = 0
463 |
464 | return Cost
465 |
466 | def addPoint(self, x, u):
467 | """at iteration j add the current point to SS, uSS and Qfun of the previous iteration
468 | Arguments:
469 | x: current state
470 | u: current input
471 | """
472 | self.SS[self.it - 1] = np.append(self.SS[self.it - 1], np.array([x + np.array([0, 0, 0, 0, self.predictiveModel.map.TrackLength, 0])]), axis=0)
473 | self.uSS[self.it - 1] = np.append(self.uSS[self.it - 1], np.array([u]),axis=0)
474 | self.Qfun[self.it - 1] = np.append(self.Qfun[self.it - 1], self.Qfun[self.it - 1][-1]-1)
475 | # The above two lines are needed as the once the predicted trajectory has crossed the finish line the goal is
476 | # to reach the end of the lap which is about to start
477 |
478 | def selectPoints(self, it, zt, numPoints):
479 | """selecte (numPoints)-nearest neivbor to zt. These states will be used to construct the safe set and the value function approximation
480 | Arguments:
481 | x: current state
482 | u: current input
483 | """
484 | x = self.SS[it]
485 | u = self.uSS[it]
486 | oneVec = np.ones((x.shape[0], 1))
487 | x0Vec = (np.dot(np.array([zt]).T, oneVec.T)).T
488 | diff = x - x0Vec
489 | norm = la.norm(diff, 1, axis=1)
490 | MinNorm = np.argmin(norm)
491 |
492 | if (MinNorm - numPoints/2 >= 0):
493 | indexSSandQfun = range(-int(numPoints/2) + MinNorm, int(numPoints/2) + MinNorm + 1)
494 | else:
495 | indexSSandQfun = range(MinNorm, MinNorm + int(numPoints))
496 |
497 | SS_Points = x[indexSSandQfun, :].T
498 | SSu_Points = u[indexSSandQfun, :].T
499 | Sel_Qfun = self.Qfun[it][indexSSandQfun]
500 |
501 | # Modify the cost if the predicion has crossed the finisch line
502 | if self.xPred == []:
503 | Sel_Qfun = self.Qfun[it][indexSSandQfun]
504 | elif (np.all((self.xPred[:, 4] > self.predictiveModel.map.TrackLength) == False)):
505 | Sel_Qfun = self.Qfun[it][indexSSandQfun]
506 | elif it < self.it - 1:
507 | Sel_Qfun = self.Qfun[it][indexSSandQfun] + self.Qfun[it][0]
508 | else:
509 | sPred = self.xPred[:, 4]
510 | predCurrLap = self.N - sum(sPred > self.predictiveModel.map.TrackLength)
511 | currLapTime = self.timeStep
512 | Sel_Qfun = self.Qfun[it][indexSSandQfun] + currLapTime + predCurrLap
513 |
514 | return SS_Points, SSu_Points, Sel_Qfun
--------------------------------------------------------------------------------
/src/fnc/controller/PredictiveModel.py:
--------------------------------------------------------------------------------
1 | from cvxopt import spmatrix, matrix, solvers
2 | from numpy import linalg as la
3 | from cvxopt.solvers import qp
4 | import numpy as np
5 | import datetime
6 | import pdb
7 | # This class is not generic and is tailored to the autonomous racing problem.
8 | # The only method need the LT-MPC and the LMPC is regressionAndLinearization, which given a state-action pair
9 | # compute the matrices A,B,C such that x_{k+1} = A x_k + Bu_k + C
10 |
11 | class PredictiveModel():
12 | def __init__(self, n, d, map, trToUse):
13 | self.map = map
14 | self.n = n # state dimension
15 | self.d = d # input dimention
16 | self.xStored = []
17 | self.uStored = []
18 | self.MaxNumPoint = 7 # max number of point per lap to use
19 | self.h = 5 # bandwidth of the Kernel for local linear regression
20 | self.lamb = 0.0 # regularization
21 | self.dt = 0.1
22 | self.scaling = np.array([[0.1, 0.0, 0.0, 0.0, 0.0],
23 | [0.0, 1.0, 0.0, 0.0, 0.0],
24 | [0.0, 0.0, 1.0, 0.0, 0.0],
25 | [0.0, 0.0, 0.0, 1.0, 0.0],
26 | [0.0, 0.0, 0.0, 0.0, 1.0]])
27 |
28 | self.stateFeatures = [0, 1, 2]
29 | self.inputFeaturesVx = [1]
30 | self.inputFeaturesLat = [0]
31 | self.usedIt = [i for i in range(trToUse)]
32 | self.lapTime = []
33 |
34 |
35 | def addTrajectory(self, x, u):
36 | if self.lapTime == [] or x.shape[0] >= self.lapTime[-1]:
37 | self.xStored.append(x)
38 | self.uStored.append(u)
39 | self.lapTime.append(x.shape[0])
40 | else:
41 | for i in range(0, len(self.xStored)):
42 | if x.shape[0] < self.lapTime[i]:
43 | self.xStored.insert(i, x)
44 | self.uStored.insert(i, u)
45 | self.lapTime.insert(i, x.shape[0])
46 | break
47 |
48 | def regressionAndLinearization(self, x, u):
49 | Ai = np.zeros((self.n, self.n))
50 | Bi = np.zeros((self.n, self.d))
51 | Ci = np.zeros(self.n)
52 |
53 | # Compute Index to use for each stored lap
54 | xuLin = np.hstack((x[self.stateFeatures], u[:]))
55 | self.indexSelected = []
56 | self.K = []
57 | for ii in self.usedIt:
58 | indexSelected_i, K_i = self.computeIndices(xuLin, ii)
59 | self.indexSelected.append(indexSelected_i)
60 | self.K.append(K_i)
61 | # print("xuLin: ",xuLin)
62 | # print("aaa indexSelected: ", self.indexSelected)
63 |
64 | # =========================
65 | # ====== Identify vx ======
66 | Q_vx, M_vx = self.compute_Q_M(self.inputFeaturesVx, self.usedIt)
67 |
68 | yIndex = 0
69 | b_vx = self.compute_b(yIndex, self.usedIt, M_vx)
70 | Ai[yIndex, self.stateFeatures], Bi[yIndex, self.inputFeaturesVx], Ci[yIndex] = self.LMPC_LocLinReg(Q_vx, b_vx, self.inputFeaturesVx)
71 |
72 | # =======================================
73 | # ====== Identify Lateral Dynamics ======
74 | Q_lat, M_lat = self.compute_Q_M(self.inputFeaturesLat, self.usedIt)
75 |
76 | yIndex = 1 # vy
77 | b_vy = self.compute_b(yIndex, self.usedIt, M_lat)
78 | Ai[yIndex, self.stateFeatures], Bi[yIndex, self.inputFeaturesLat], Ci[yIndex] = self.LMPC_LocLinReg(Q_lat, b_vy, self.inputFeaturesLat)
79 |
80 | yIndex = 2 # wz
81 | b_wz = self.compute_b(yIndex, self.usedIt, M_lat)
82 | Ai[yIndex, self.stateFeatures], Bi[yIndex, self.inputFeaturesLat], Ci[yIndex] = self.LMPC_LocLinReg(Q_lat, b_wz, self.inputFeaturesLat)
83 |
84 | # ===========================
85 | # ===== Linearization =======
86 | vx = x[0]; vy = x[1]
87 | wz = x[2]; epsi = x[3]
88 | s = x[4]; ey = x[5]
89 | dt = self.dt
90 |
91 | if s < 0:
92 | print("s is negative, here the state: \n", x)
93 |
94 | startTimer = datetime.datetime.now() # Start timer for LMPC iteration
95 | cur = self.map.curvature(s)
96 | cur = self.map.curvature(s)
97 | den = 1 - cur * ey
98 |
99 | # ===========================
100 | # ===== Linearize epsi ======
101 | # epsi_{k+1} = epsi + dt * ( wz - (vx * np.cos(epsi) - vy * np.sin(epsi)) / (1 - cur * ey) * cur )
102 | depsi_vx = -dt * np.cos(epsi) / den * cur
103 | depsi_vy = dt * np.sin(epsi) / den * cur
104 | depsi_wz = dt
105 | depsi_epsi = 1 - dt * (-vx * np.sin(epsi) - vy * np.cos(epsi)) / den * cur
106 | depsi_s = 0 # Because cur = constant
107 | depsi_ey = dt * (vx * np.cos(epsi) - vy * np.sin(epsi)) / (den ** 2) * cur * (-cur)
108 |
109 | Ai[3, :] = [depsi_vx, depsi_vy, depsi_wz, depsi_epsi, depsi_s, depsi_ey]
110 | Ci[3] = epsi + dt * (wz - (vx * np.cos(epsi) - vy * np.sin(epsi)) / (1 - cur * ey) * cur) - np.dot(Ai[3, :], x)
111 | # ===========================
112 | # ===== Linearize s =========
113 | # s_{k+1} = s + dt * ( (vx * np.cos(epsi) - vy * np.sin(epsi)) / (1 - cur * ey) )
114 | ds_vx = dt * (np.cos(epsi) / den)
115 | ds_vy = -dt * (np.sin(epsi) / den)
116 | ds_wz = 0
117 | ds_epsi = dt * (-vx * np.sin(epsi) - vy * np.cos(epsi)) / den
118 | ds_s = 1 # + Ts * (Vx * cos(epsi) - Vy * sin(epsi)) / (1 - ey * rho) ^ 2 * (-ey * drho);
119 | ds_ey = -dt * (vx * np.cos(epsi) - vy * np.sin(epsi)) / (den ** 2) * (-cur)
120 |
121 | Ai[4, :] = [ds_vx, ds_vy, ds_wz, ds_epsi, ds_s, ds_ey]
122 | Ci[4] = s + dt * ((vx * np.cos(epsi) - vy * np.sin(epsi)) / (1 - cur * ey)) - np.dot(Ai[4, :], x)
123 |
124 | # ===========================
125 | # ===== Linearize ey ========
126 | # ey_{k+1} = ey + dt * (vx * np.sin(epsi) + vy * np.cos(epsi))
127 | dey_vx = dt * np.sin(epsi)
128 | dey_vy = dt * np.cos(epsi)
129 | dey_wz = 0
130 | dey_epsi = dt * (vx * np.cos(epsi) - vy * np.sin(epsi))
131 | dey_s = 0
132 | dey_ey = 1
133 |
134 | Ai[5, :] = [dey_vx, dey_vy, dey_wz, dey_epsi, dey_s, dey_ey]
135 | Ci[5] = ey + dt * (vx * np.sin(epsi) + vy * np.cos(epsi)) - np.dot(Ai[5, :], x)
136 |
137 | endTimer = datetime.datetime.now(); deltaTimer_tv = endTimer - startTimer
138 |
139 | return Ai, Bi, Ci
140 |
141 | def compute_Q_M(self, inputFeatures, usedIt):
142 | Counter = 0
143 | X0 = np.empty((0,len(self.stateFeatures)+len(inputFeatures)))
144 | Ktot = np.empty((0))
145 |
146 | for it in usedIt:
147 | X0 = np.append( X0, np.hstack((self.xStored[it][np.ix_(self.indexSelected[Counter], self.stateFeatures)],self.uStored[it][np.ix_(self.indexSelected[Counter], inputFeatures)])), axis=0 )
148 | Ktot = np.append(Ktot, self.K[Counter])
149 | Counter += 1
150 |
151 | M = np.hstack( (X0, np.ones((X0.shape[0], 1))) )
152 | Q0 = np.dot(np.dot(M.T, np.diag(Ktot)), M)
153 | Q = matrix(Q0 + self.lamb * np.eye(Q0.shape[0]))
154 |
155 | return Q, M
156 |
157 | def compute_b(self, yIndex, usedIt, M):
158 | Counter = 0
159 | y = np.empty((0))
160 | Ktot = np.empty((0))
161 |
162 | for it in usedIt:
163 | y = np.append(y, np.squeeze(self.xStored[it][self.indexSelected[Counter] + 1, yIndex]))
164 | Ktot = np.append(Ktot, self.K[Counter])
165 | Counter += 1
166 |
167 | b = matrix(-np.dot(np.dot(M.T, np.diag(Ktot)), y))
168 | return b
169 |
170 | def LMPC_LocLinReg(self, Q, b, inputFeatures):
171 | # Solve QP
172 | res_cons = qp(Q, b) # This is ordered as [A B C]
173 | # Unpack results
174 | result = np.squeeze(np.array(res_cons['x']))
175 | A = result[0:len(self.stateFeatures)]
176 | B = result[len(self.stateFeatures):(len(self.stateFeatures)+len(inputFeatures))]
177 | C = result[-1]
178 | return A, B, C
179 |
180 | def computeIndices(self, x, it):
181 | oneVec = np.ones( (self.xStored[it].shape[0]-1, 1) )
182 | xVec = (np.dot( np.array([x]).T, oneVec.T )).T
183 | DataMatrix = np.hstack((self.xStored[it][0:-1, self.stateFeatures], self.uStored[it][0:-1, :]))
184 |
185 | diff = np.dot(( DataMatrix - xVec ), self.scaling)
186 | norm = la.norm(diff, 1, axis=1)
187 | indexTot = np.squeeze(np.where(norm < self.h))
188 | if (indexTot.shape[0] >= self.MaxNumPoint):
189 | index = np.argsort(norm)[0:self.MaxNumPoint]
190 | else:
191 | index = indexTot
192 |
193 | K = ( 1 - ( norm[index] / self.h )**2 ) * 3/4
194 | # if norm.shape[0]<500:
195 | # print("norm: ", norm, norm.shape)
196 |
197 | return index, K
--------------------------------------------------------------------------------
/src/fnc/plot.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import matplotlib.pyplot as plt
3 | from matplotlib.animation import FuncAnimation
4 | import matplotlib.patches as patches
5 |
6 | import pdb
7 |
8 | def plotTrajectory(map, x, x_glob, u, stringTitle):
9 | Points = int(np.floor(10 * (map.PointAndTangent[-1, 3] + map.PointAndTangent[-1, 4])))
10 | Points1 = np.zeros((Points, 2))
11 | Points2 = np.zeros((Points, 2))
12 | Points0 = np.zeros((Points, 2))
13 | for i in range(0, int(Points)):
14 | Points1[i, :] = map.getGlobalPosition(i * 0.1, map.halfWidth)
15 | Points2[i, :] = map.getGlobalPosition(i * 0.1, -map.halfWidth)
16 | Points0[i, :] = map.getGlobalPosition(i * 0.1, 0)
17 |
18 | plt.figure()
19 | plt.plot(map.PointAndTangent[:, 0], map.PointAndTangent[:, 1], 'o')
20 | plt.plot(Points0[:, 0], Points0[:, 1], '--')
21 | plt.plot(Points1[:, 0], Points1[:, 1], '-b')
22 | plt.plot(Points2[:, 0], Points2[:, 1], '-b')
23 | plt.plot(x_glob[:, 4], x_glob[:, 5], '-r')
24 | plt.title(stringTitle)
25 |
26 | plt.figure()
27 | plt.subplot(711)
28 | plt.plot(x[:, 4], x[:, 0], '-o')
29 | plt.ylabel('vx')
30 | plt.subplot(712)
31 | plt.plot(x[:, 4], x[:, 1], '-o')
32 | plt.ylabel('vy')
33 | plt.subplot(713)
34 | plt.plot(x[:, 4], x[:, 2], '-o')
35 | plt.ylabel('wz')
36 | plt.subplot(714)
37 | plt.plot(x[:, 4], x[:, 3], '-o')
38 | plt.ylabel('epsi')
39 | plt.subplot(715)
40 | plt.plot(x[:, 4], x[:, 5], '-o')
41 | plt.ylabel('ey')
42 | plt.subplot(716)
43 | plt.plot(x[:, 4], u[:, 0], '-o')
44 | plt.ylabel('steering')
45 | plt.subplot(717)
46 | plt.plot(x[:, 4], u[:, 1], '-o')
47 | plt.ylabel('acc')
48 | plt.title(stringTitle)
49 |
50 | def plotClosedLoopLMPC(lmpc, map):
51 | SS_glob = lmpc.SS_glob
52 | LapTime = lmpc.LapTime
53 | SS = lmpc.SS
54 | uSS = lmpc.uSS
55 |
56 | TotNumberIt = lmpc.it
57 | Points = int(np.floor(10 * (map.PointAndTangent[-1, 3] + map.PointAndTangent[-1, 4])))
58 | Points1 = np.zeros((Points, 2))
59 | Points2 = np.zeros((Points, 2))
60 | Points0 = np.zeros((Points, 2))
61 | for i in range(0, int(Points)):
62 | Points1[i, :] = map.getGlobalPosition(i * 0.1, map.halfWidth)
63 | Points2[i, :] = map.getGlobalPosition(i * 0.1, -map.halfWidth)
64 | Points0[i, :] = map.getGlobalPosition(i * 0.1, 0)
65 |
66 | plt.figure()
67 | plt.plot(map.PointAndTangent[:, 0], map.PointAndTangent[:, 1], 'o')
68 | plt.plot(Points0[:, 0], Points0[:, 1], '--')
69 | plt.plot(Points1[:, 0], Points1[:, 1], '-b')
70 | plt.plot(Points2[:, 0], Points2[:, 1], '-b')
71 |
72 | for i in range(TotNumberIt-5, TotNumberIt):
73 | plt.plot(SS_glob[i][0:LapTime[i], 4], SS_glob[i][0:LapTime[i], 5], '-r')
74 |
75 | plt.figure()
76 | plt.subplot(711)
77 | for i in range(2, TotNumberIt):
78 | plt.plot(SS[i][0:LapTime[i], 4], SS[i][0:LapTime[i], 0], '-o')
79 | plt.ylabel('vx')
80 | plt.subplot(712)
81 | for i in range(2, TotNumberIt):
82 | plt.plot(SS[i][0:LapTime[i], 4], SS[i][0:LapTime[i], 1], '-o')
83 | plt.ylabel('vy')
84 | plt.subplot(713)
85 | for i in range(2, TotNumberIt):
86 | plt.plot(SS[i][0:LapTime[i], 4], SS[i][0:LapTime[i], 2], '-o')
87 | plt.ylabel('wz')
88 | plt.subplot(714)
89 | for i in range(2, TotNumberIt):
90 | plt.plot(SS[i][0:LapTime[i], 4], SS[i][0:LapTime[i], 3], '-o')
91 | plt.ylabel('epsi')
92 | plt.subplot(715)
93 | for i in range(2, TotNumberIt):
94 | plt.plot(SS[i][0:LapTime[i], 4], SS[i][0:LapTime[i], 5], '-o')
95 | plt.ylabel('ey')
96 | plt.subplot(716)
97 | for i in range(2, TotNumberIt):
98 | plt.plot(uSS[i][0:LapTime[i] - 1, 0], '-o')
99 | plt.ylabel('Steering')
100 | plt.subplot(717)
101 | for i in range(2, TotNumberIt):
102 | plt.plot(uSS[i][0:LapTime[i] - 1, 1], '-o')
103 | plt.ylabel('Acc')
104 |
105 |
106 | def animation_xy(map, lmpc, it):
107 | SS_glob = lmpc.SS_glob
108 | LapTime = lmpc.LapTime
109 | SS = lmpc.SS
110 | uSS = lmpc.uSS
111 |
112 | Points = int(np.floor(10 * (map.PointAndTangent[-1, 3] + map.PointAndTangent[-1, 4])))
113 | Points1 = np.zeros((Points, 2))
114 | Points2 = np.zeros((Points, 2))
115 | Points0 = np.zeros((Points, 2))
116 |
117 | for i in range(0, int(Points)):
118 | Points1[i, :] = map.getGlobalPosition(i * 0.1, map.halfWidth)
119 | Points2[i, :] = map.getGlobalPosition(i * 0.1, -map.halfWidth)
120 | Points0[i, :] = map.getGlobalPosition(i * 0.1, 0)
121 |
122 | plt.figure(200)
123 | plt.plot(map.PointAndTangent[:, 0], map.PointAndTangent[:, 1], 'o')
124 | plt.plot(Points0[:, 0], Points0[:, 1], '--')
125 | plt.plot(Points1[:, 0], Points1[:, 1], '-b')
126 | plt.plot(Points2[:, 0], Points2[:, 1], '-b')
127 | plt.plot(SS_glob[it][0:LapTime[it], 4], SS_glob[it][0:LapTime[it], 5], '-ok', label="Closed-loop trajectory",zorder=-1)
128 |
129 | ax = plt.axes()
130 | SSpoints_x = []; SSpoints_y = []
131 | xPred = []; yPred = []
132 | SSpoints, = ax.plot(SSpoints_x, SSpoints_y, 'sb', label="SS",zorder=0)
133 | line, = ax.plot(xPred, yPred, '-or', label="Predicted Trajectory",zorder=1)
134 |
135 | v = np.array([[ 1., 1.],
136 | [ 1., -1.],
137 | [-1., -1.],
138 | [-1., 1.]])
139 | rec = patches.Polygon(v, alpha=0.7,closed=True, fc='r', ec='k',zorder=10)
140 | ax.add_patch(rec)
141 |
142 | plt.legend(bbox_to_anchor=(0,1.02,1,0.2), loc="lower left",
143 | mode="expand", borderaxespad=0, ncol=3)
144 |
145 |
146 | N = lmpc.N
147 | numSS_Points = lmpc.numSS_Points
148 | for i in range(0, int(lmpc.LapTime[it])):
149 |
150 | xPred = np.zeros((N+1, 1)); yPred = np.zeros((N+1, 1))
151 | SSpoints_x = np.zeros((numSS_Points, 1)); SSpoints_y = np.zeros((numSS_Points, 1))
152 |
153 | for j in range(0, N+1):
154 | xPred[j,0], yPred[j,0] = map.getGlobalPosition( lmpc.xStoredPredTraj[it][i][j, 4],
155 | lmpc.xStoredPredTraj[it][i][j, 5] )
156 |
157 | if j == 0:
158 | x = SS_glob[it][i, 4]
159 | y = SS_glob[it][i, 5]
160 | psi = SS_glob[it][i, 3]
161 | l = 0.4; w = 0.2
162 | car_x = [ x + l * np.cos(psi) - w * np.sin(psi), x + l*np.cos(psi) + w * np.sin(psi),
163 | x - l * np.cos(psi) + w * np.sin(psi), x - l * np.cos(psi) - w * np.sin(psi)]
164 | car_y = [ y + l * np.sin(psi) + w * np.cos(psi), y + l * np.sin(psi) - w * np.cos(psi),
165 | y - l * np.sin(psi) - w * np.cos(psi), y - l * np.sin(psi) + w * np.cos(psi)]
166 |
167 | for j in range(0, numSS_Points):
168 | SSpoints_x[j,0], SSpoints_y[j,0] = map.getGlobalPosition(lmpc.SSStoredPredTraj[it][i][j, 4],
169 | lmpc.SSStoredPredTraj[it][i][j, 5])
170 | SSpoints.set_data(SSpoints_x, SSpoints_y)
171 |
172 | line.set_data(xPred, yPred)
173 | rec.set_xy(np.array([car_x, car_y]).T)
174 | plt.draw()
175 | plt.pause(1e-17)
176 |
177 | def animation_states(map, LMPCOpenLoopData, lmpc, it):
178 | SS_glob = lmpc.SS_glob
179 | LapTime = lmpc.LapTime
180 | SS = lmpc.SS
181 | uSS = lmpc.uSS
182 |
183 | xdata = []; ydata = []
184 | fig = plt.figure(100)
185 |
186 | axvx = fig.add_subplot(3, 2, 1)
187 | plt.plot(SS[0:LapTime[it], 4, it], SS[0:LapTime[it], 0, it], '-ok', label="Closed-loop trajectory")
188 | lineSSvx, = axvx.plot(xdata, ydata, 'sb-', label="SS")
189 | linevx, = axvx.plot(xdata, ydata, 'or-', label="Predicted Trajectory")
190 | plt.ylabel("vx")
191 | plt.xlabel("s")
192 |
193 | plt.legend(bbox_to_anchor=(0,1.02,1,0.2), loc="lower left",
194 | mode="expand", borderaxespad=0, ncol=3)
195 |
196 | axvy = fig.add_subplot(3, 2, 2)
197 | axvy.plot(SS[0:LapTime[it], 4, it], SS[0:LapTime[it], 1, it], '-ok')
198 | lineSSvy, = axvy.plot(xdata, ydata, 'sb-')
199 | linevy, = axvy.plot(xdata, ydata, 'or-')
200 | plt.ylabel("vy")
201 | plt.xlabel("s")
202 |
203 | axwz = fig.add_subplot(3, 2, 3)
204 | axwz.plot(SS[0:LapTime[it], 4, it], SS[0:LapTime[it], 2, it], '-ok')
205 | lineSSwz, = axwz.plot(xdata, ydata, 'sb-')
206 | linewz, = axwz.plot(xdata, ydata, 'or-')
207 | plt.ylabel("wz")
208 | plt.xlabel("s")
209 |
210 | axepsi = fig.add_subplot(3, 2, 4)
211 | axepsi.plot(SS[0:LapTime[it], 4, it], SS[0:LapTime[it], 3, it], '-ok')
212 | lineSSepsi, = axepsi.plot(xdata, ydata, 'sb-')
213 | lineepsi, = axepsi.plot(xdata, ydata, 'or-')
214 | plt.ylabel("epsi")
215 | plt.xlabel("s")
216 |
217 | axey = fig.add_subplot(3, 2, 5)
218 | axey.plot(SS[0:LapTime[it], 4, it], SS[0:LapTime[it], 5, it], '-ok')
219 | lineSSey, = axey.plot(xdata, ydata, 'sb-')
220 | lineey, = axey.plot(xdata, ydata, 'or-')
221 | plt.ylabel("ey")
222 | plt.xlabel("s")
223 |
224 | Points = np.floor(10 * (map.PointAndTangent[-1, 3] + map.PointAndTangent[-1, 4]))
225 | Points1 = np.zeros((Points, 2))
226 | Points2 = np.zeros((Points, 2))
227 | Points0 = np.zeros((Points, 2))
228 | for i in range(0, int(Points)):
229 | Points1[i, :] = map.getGlobalPosition(i * 0.1, map.halfWidth)
230 | Points2[i, :] = map.getGlobalPosition(i * 0.1, -map.halfWidth)
231 | Points0[i, :] = map.getGlobalPosition(i * 0.1, 0)
232 |
233 | axtr = fig.add_subplot(3, 2, 6)
234 | plt.plot(map.PointAndTangent[:, 0], map.PointAndTangent[:, 1], 'o')
235 | plt.plot(Points0[:, 0], Points0[:, 1], '--')
236 | plt.plot(Points1[:, 0], Points1[:, 1], '-b')
237 | plt.plot(Points2[:, 0], Points2[:, 1], '-b')
238 | plt.plot(SS_glob[it][0:LapTime[it], 4], SS_glob[it][0:LapTime[it], 5], '-ok')
239 |
240 | SSpoints_x = []; SSpoints_y = []
241 | xPred = []; yPred = []
242 | SSpoints_tr, = axtr.plot(SSpoints_x, SSpoints_y, 'sb')
243 | line_tr, = axtr.plot(xPred, yPred, '-or')
244 |
245 | N = lmpc.N
246 | numSS_Points = lmpc.numSS_Points
247 | for i in range(0, int(lmpc.LapTime[it])):
248 |
249 | xPred = lmpc.xStoredPredTraj[it][i]
250 | SSpoints = lmpc.SSStoredPredTraj[it][i]
251 |
252 | linevx.set_data(xPred[:, 4], xPred[:, 0]); axvx.set_title(str(xPred[0, 0]))
253 | linevy.set_data(xPred[:, 4], xPred[:, 1]); axvy.set_title(str(xPred[0, 1]))
254 | linewz.set_data(xPred[:, 4], xPred[:, 2]); axwz.set_title(str(xPred[0, 2]))
255 | lineepsi.set_data(xPred[:, 4], xPred[:, 3]); axepsi.set_title(str(xPred[0, 3]))
256 | lineey.set_data(xPred[:, 4], xPred[:, 5]); axey.set_title(str(xPred[0, 5]))
257 |
258 | epsiReal = xPred[0, 3]
259 |
260 | lineSSvx.set_data(SSpoints[4,:], SSpoints[0,:])
261 | lineSSvy.set_data(SSpoints[4,:], SSpoints[1,:])
262 | lineSSwz.set_data(SSpoints[4,:], SSpoints[2,:])
263 | lineSSepsi.set_data(SSpoints[4,:], SSpoints[3,:])
264 | lineSSey.set_data(SSpoints[4,:], SSpoints[5,:])
265 |
266 | xPred = np.zeros((N + 1, 1));yPred = np.zeros((N + 1, 1))
267 | SSpoints_x = np.zeros((numSS_Points, 1));SSpoints_y = np.zeros((numSS_Points, 1))
268 |
269 | for j in range(0, N + 1):
270 | xPred[j, 0], yPred[j, 0] = map.getGlobalPosition(lmpc.xStoredPredTraj[it][i][j, 4],
271 | lmpc.xStoredPredTraj[it][i][j, 5])
272 |
273 | for j in range(0, numSS_Points):
274 | SSpoints_x[j, 0], SSpoints_y[j, 0] = map.getGlobalPosition(LMPCOpenLoopData.SSused[4, j, i, it],
275 | LMPCOpenLoopData.SSused[5, j, i, it])
276 |
277 | line_tr.set_data(xPred, yPred)
278 |
279 |
280 | vec = np.array([xPred[0, 0], yPred[0, 0]]) - np.array([SS_glob[i, 4, it], SS_glob[i, 5, it]])
281 |
282 | s, ey, epsi, _ = map.getLocalPosition( SS_glob[i, 4, it], SS_glob[i, 5, it], SS_glob[i, 3, it])
283 | axtr.set_title(str(s)+" "+str(ey)+" "+str(epsi))
284 |
285 | # axepsi.set_title(str(epsiReal)+" "+str(epsi))
286 | SSpoints_tr.set_data(SSpoints_x, SSpoints_y)
287 |
288 | plt.draw()
289 | plt.pause(1e-17)
290 |
291 | def saveGif_xyResults(map, LMPCOpenLoopData, lmpc, it):
292 | SS_glob = lmpc.SS_glob
293 | LapTime = lmpc.LapTime
294 | SS = lmpc.SS
295 | uSS = lmpc.uSS
296 |
297 | Points = int(np.floor(10 * (map.PointAndTangent[-1, 3] + map.PointAndTangent[-1, 4])))
298 | Points1 = np.zeros((Points, 2))
299 | Points2 = np.zeros((Points, 2))
300 | Points0 = np.zeros((Points, 2))
301 | for i in range(0, int(Points)):
302 | Points1[i, :] = map.getGlobalPosition(i * 0.1, map.halfWidth)
303 | Points2[i, :] = map.getGlobalPosition(i * 0.1, -map.halfWidth)
304 | Points0[i, :] = map.getGlobalPosition(i * 0.1, 0)
305 |
306 | fig = plt.figure(101)
307 | # plt.ylim((-5, 1.5))
308 | fig.set_tight_layout(True)
309 | plt.plot(map.PointAndTangent[:, 0], map.PointAndTangent[:, 1], 'o')
310 | plt.plot(Points0[:, 0], Points0[:, 1], '--')
311 | plt.plot(Points1[:, 0], Points1[:, 1], '-b')
312 | plt.plot(Points2[:, 0], Points2[:, 1], '-b')
313 | plt.plot(SS_glob[it][0:LapTime[it], 4], SS_glob[it][0:LapTime[it], 5], '-ok', label="Closed-loop trajectory", markersize=1,zorder=-1)
314 |
315 | ax = plt.axes()
316 | SSpoints_x = []; SSpoints_y = []
317 | xPred = []; yPred = []
318 | SSpoints, = ax.plot(SSpoints_x, SSpoints_y, 'og', label="SS",zorder=0)
319 | line, = ax.plot(xPred, yPred, '-or', label="Predicted Trajectory",zorder=1)
320 |
321 | v = np.array([[ 1., 1.],
322 | [ 1., -1.],
323 | [-1., -1.],
324 | [-1., 1.]])
325 | rec = patches.Polygon(v, alpha=0.7,closed=True, fc='g', ec='k',zorder=10)
326 | ax.add_patch(rec)
327 |
328 | plt.legend(mode="expand", ncol=3)
329 | plt.title('Lap: '+str(it))
330 | # plt.legend(bbox_to_anchor=(0,1.02,1,0.2), loc="lower left",
331 | # mode="expand", borderaxespad=0, ncol=3)
332 |
333 | N = lmpc.N
334 | numSS_Points = lmpc.numSS_Points
335 |
336 | def update(i):
337 | xPred = np.zeros((N + 1, 1)); yPred = np.zeros((N + 1, 1))
338 | SSpoints_x = np.zeros((numSS_Points, 1)); SSpoints_y = np.zeros((numSS_Points, 1))
339 |
340 | for j in range(0, N + 1):
341 | xPred[j, 0], yPred[j, 0] = map.getGlobalPosition(lmpc.xStoredPredTraj[it][i][j, 4],
342 | lmpc.xStoredPredTraj[it][i][j, 5])
343 |
344 | if j == 0:
345 | x = SS_glob[i, 4, it]
346 | y = SS_glob[i, 5, it]
347 | psi = SS_glob[i, 3, it]
348 | l = 0.4;w = 0.2
349 | car_x = [x + l * np.cos(psi) - w * np.sin(psi), x + l * np.cos(psi) + w * np.sin(psi),
350 | x - l * np.cos(psi) + w * np.sin(psi), x - l * np.cos(psi) - w * np.sin(psi)]
351 | car_y = [y + l * np.sin(psi) + w * np.cos(psi), y + l * np.sin(psi) - w * np.cos(psi),
352 | y - l * np.sin(psi) - w * np.cos(psi), y - l * np.sin(psi) + w * np.cos(psi)]
353 |
354 | for j in range(0, numSS_Points):
355 | SSpoints_x[j, 0], SSpoints_y[j, 0] = map.getGlobalPosition(LMPCOpenLoopData.SSused[4, j, i, it],
356 | LMPCOpenLoopData.SSused[5, j, i, it])
357 | SSpoints.set_data(SSpoints_x, SSpoints_y)
358 |
359 | line.set_data(xPred, yPred)
360 |
361 | rec.set_xy(np.array([car_x, car_y]).T)
362 |
363 | anim = FuncAnimation(fig, update, frames=np.arange(0, int(lmpc.LapTime[it])), interval=100)
364 |
365 | anim.save('ClosedLoop.gif', dpi=80, writer='imagemagick')
366 |
--------------------------------------------------------------------------------
/src/fnc/simulator/SysModel.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import pdb
3 | import datetime
4 | from Utilities import wrap
5 |
6 | class Simulator():
7 | """Vehicle simulator
8 | Attributes:
9 | Sim: given a Controller object run closed-loop simulation and write the data in the ClosedLoopData object
10 | """
11 | def __init__(self, map, dt = 0.1, multiLap = True, flagLMPC = False):
12 | """Initialization
13 | map: map
14 | lap: number of laps to run. If set to 0 then the simulation is completed when ClosedLoopData is full
15 | flagLMPC: set to 0 for standart controller. Set to 1 for LMPC --> at iteration j add data to SS^{j-1} (look line 9999)
16 | """
17 | self.map = map
18 | self.multiLap = multiLap
19 | self.flagLMPC = flagLMPC
20 | self.dt = dt
21 |
22 | def sim(self, x0, Controller, maxSimTime = 100):
23 | """Simulate closed-loop system
24 | """
25 | x_cl = [x0[0]]
26 | x_cl_glob = [x0[1]]
27 | u_cl = []
28 | sim_t = 0
29 | SimulationTime = 0
30 |
31 | i=0
32 | flagExt = False
33 | while (i (self.map.TrackLength)):
46 | print("Lap completed")
47 | flagExt = True
48 | i += 1
49 |
50 | xF = [np.array(x_cl[-1]) - np.array([0, 0, 0, 0, self.map.TrackLength, 0]), np.array(x_cl_glob[-1])]
51 | x_cl.pop()
52 | x_cl_glob.pop()
53 |
54 | return np.array(x_cl), np.array(u_cl), np.array(x_cl_glob), xF
55 |
56 | def dynModel(self, x, x_glob, u):
57 | # This method computes the system evolution. Note that the discretization is deltaT and therefore is needed that
58 | # dt <= deltaT and ( dt / deltaT) = integer value
59 |
60 | # Vehicle Parameters
61 | m = 1.98
62 | lf = 0.125
63 | lr = 0.125
64 | Iz = 0.024
65 | Df = 0.8 * m * 9.81 / 2.0
66 | Cf = 1.25
67 | Bf = 1.0
68 | Dr = 0.8 * m * 9.81 / 2.0
69 | Cr = 1.25
70 | Br = 1.0
71 |
72 | # Discretization Parameters
73 | deltaT = 0.001
74 | x_next = np.zeros(x.shape[0])
75 | cur_x_next = np.zeros(x.shape[0])
76 |
77 | # Extract the value of the states
78 | delta = u[0]
79 | a = u[1]
80 |
81 | psi = x_glob[3]
82 | X = x_glob[4]
83 | Y = x_glob[5]
84 |
85 | vx = x[0]
86 | vy = x[1]
87 | wz = x[2]
88 | epsi = x[3]
89 | s = x[4]
90 | ey = x[5]
91 |
92 | # Initialize counter
93 | i = 0
94 | while( (i+1) * deltaT <= self.dt):
95 | # Compute tire split angle
96 | alpha_f = delta - np.arctan2( vy + lf * wz, vx )
97 | alpha_r = - np.arctan2( vy - lf * wz , vx)
98 |
99 | # Compute lateral force at front and rear tire
100 | Fyf = Df * np.sin( Cf * np.arctan(Bf * alpha_f ) )
101 | Fyr = Dr * np.sin( Cr * np.arctan(Br * alpha_r ) )
102 |
103 | # Propagate the dynamics of deltaT
104 | x_next[0] = vx + deltaT * (a - 1 / m * Fyf * np.sin(delta) + wz*vy)
105 | x_next[1] = vy + deltaT * (1 / m * (Fyf * np.cos(delta) + Fyr) - wz * vx)
106 | x_next[2] = wz + deltaT * (1 / Iz *(lf * Fyf * np.cos(delta) - lr * Fyr) )
107 | x_next[3] = psi + deltaT * (wz)
108 | x_next[4] = X + deltaT * ((vx * np.cos(psi) - vy * np.sin(psi)))
109 | x_next[5] = Y + deltaT * (vx * np.sin(psi) + vy * np.cos(psi))
110 |
111 | cur = self.map.curvature(s)
112 | cur_x_next[0] = vx + deltaT * (a - 1 / m * Fyf * np.sin(delta) + wz*vy)
113 | cur_x_next[1] = vy + deltaT * (1 / m * (Fyf * np.cos(delta) + Fyr) - wz * vx)
114 | cur_x_next[2] = wz + deltaT * (1 / Iz *(lf * Fyf * np.cos(delta) - lr * Fyr) )
115 | cur_x_next[3] = epsi + deltaT * ( wz - (vx * np.cos(epsi) - vy * np.sin(epsi)) / (1 - cur * ey) * cur )
116 | cur_x_next[4] = s + deltaT * ( (vx * np.cos(epsi) - vy * np.sin(epsi)) / (1 - cur * ey) )
117 | cur_x_next[5] = ey + deltaT * (vx * np.sin(epsi) + vy * np.cos(epsi))
118 |
119 | # Update the value of the states
120 | psi = x_next[3]
121 | X = x_next[4]
122 | Y = x_next[5]
123 |
124 | vx = cur_x_next[0]
125 | vy = cur_x_next[1]
126 | wz = cur_x_next[2]
127 | epsi = cur_x_next[3]
128 | s = cur_x_next[4]
129 | ey = cur_x_next[5]
130 |
131 | if (s < 0):
132 | print("Start Point: ", x, " Input: ", u)
133 | print("x_next: ", x_next)
134 |
135 | # Increment counter
136 | i = i+1
137 |
138 | # Noises
139 | noise_vx = np.max([-0.05, np.min([np.random.randn() * 0.01, 0.05])])
140 | noise_vy = np.max([-0.05, np.min([np.random.randn() * 0.01, 0.05])])
141 | noise_wz = np.max([-0.05, np.min([np.random.randn() * 0.005, 0.05])])
142 |
143 | cur_x_next[0] = cur_x_next[0] + 0.01*noise_vx
144 | cur_x_next[1] = cur_x_next[1] + 0.01*noise_vy
145 | cur_x_next[2] = cur_x_next[2] + 0.01*noise_wz
146 |
147 | return cur_x_next, x_next
148 |
149 |
150 |
151 |
--------------------------------------------------------------------------------
/src/fnc/simulator/Track.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import numpy.linalg as la
3 | import pdb
4 |
5 | class Map():
6 | """map object
7 | Attributes:
8 | getGlobalPosition: convert position from (s, ey) to (X,Y)
9 | """
10 | def __init__(self, halfWidth):
11 | """Initialization
12 | halfWidth: track halfWidth
13 | Modify the vector spec to change the geometry of the track
14 | """
15 | # Goggle-shaped track
16 | # self.slack = 0.15
17 | # self.halfWidth = halfWidth
18 | # spec = np.array([[60 * 0.03, 0],
19 | # [80 * 0.03, -80 * 0.03 * 2 / np.pi],
20 | # # Note s = 1 * np.pi / 2 and r = -1 ---> Angle spanned = np.pi / 2
21 | # [20 * 0.03, 0],
22 | # [80 * 0.03, -80 * 0.03 * 2 / np.pi],
23 | # [40 * 0.03, +40 * 0.03 * 10 / np.pi],
24 | # [60 * 0.03, -60 * 0.03 * 5 / np.pi],
25 | # [40 * 0.03, +40 * 0.03 * 10 / np.pi],
26 | # [80 * 0.03, -80 * 0.03 * 2 / np.pi],
27 | # [20 * 0.03, 0],
28 | # [80 * 0.03, -80 * 0.03 * 2 / np.pi]])
29 |
30 | # L-shaped track
31 | self.halfWidth = 0.4
32 | self.slack = 0.45
33 | lengthCurve = 4.5
34 | spec = np.array([[1.0, 0],
35 | [lengthCurve, lengthCurve / np.pi],
36 | # Note s = 1 * np.pi / 2 and r = -1 ---> Angle spanned = np.pi / 2
37 | [lengthCurve / 2, -lengthCurve / np.pi],
38 | [lengthCurve, lengthCurve / np.pi],
39 | [lengthCurve / np.pi * 2, 0],
40 | [lengthCurve / 2, lengthCurve / np.pi]])
41 |
42 |
43 | # spec = np.array([[1.0, 0],
44 | # [4.5, -4.5 / np.pi],
45 | # # Note s = 1 * np.pi / 2 and r = -1 ---> Angle spanned = np.pi / 2
46 | # [2.0, 0],
47 | # [4.5, -4.5 / np.pi],
48 | # [1.0, 0]])
49 |
50 | # Now given the above segments we compute the (x, y) points of the track and the angle of the tangent vector (psi) at
51 | # these points. For each segment we compute the (x, y, psi) coordinate at the last point of the segment. Furthermore,
52 | # we compute also the cumulative s at the starting point of the segment at signed curvature
53 | # PointAndTangent = [x, y, psi, cumulative s, segment length, signed curvature]
54 | PointAndTangent = np.zeros((spec.shape[0] + 1, 6))
55 | for i in range(0, spec.shape[0]):
56 | if spec[i, 1] == 0.0: # If the current segment is a straight line
57 | l = spec[i, 0] # Length of the segments
58 | if i == 0:
59 | ang = 0 # Angle of the tangent vector at the starting point of the segment
60 | x = 0 + l * np.cos(ang) # x coordinate of the last point of the segment
61 | y = 0 + l * np.sin(ang) # y coordinate of the last point of the segment
62 | else:
63 | ang = PointAndTangent[i - 1, 2] # Angle of the tangent vector at the starting point of the segment
64 | x = PointAndTangent[i-1, 0] + l * np.cos(ang) # x coordinate of the last point of the segment
65 | y = PointAndTangent[i-1, 1] + l * np.sin(ang) # y coordinate of the last point of the segment
66 | psi = ang # Angle of the tangent vector at the last point of the segment
67 |
68 |
69 | if i == 0:
70 | NewLine = np.array([x, y, psi, PointAndTangent[i, 3], l, 0])
71 | else:
72 | NewLine = np.array([x, y, psi, PointAndTangent[i-1, 3] + PointAndTangent[i-1, 4], l, 0])
73 |
74 | PointAndTangent[i, :] = NewLine # Write the new info
75 | else:
76 | l = spec[i, 0] # Length of the segment
77 | r = spec[i, 1] # Radius of curvature
78 |
79 |
80 | if r >= 0:
81 | direction = 1
82 | else:
83 | direction = -1
84 |
85 | if i == 0:
86 | ang = 0 # Angle of the tangent vector at the
87 | # starting point of the segment
88 | CenterX = 0 \
89 | + np.abs(r) * np.cos(ang + direction * np.pi / 2) # x coordinate center of circle
90 | CenterY = 0 \
91 | + np.abs(r) * np.sin(ang + direction * np.pi / 2) # y coordinate center of circle
92 | else:
93 | ang = PointAndTangent[i - 1, 2] # Angle of the tangent vector at the
94 | # starting point of the segment
95 | CenterX = PointAndTangent[i-1, 0] \
96 | + np.abs(r) * np.cos(ang + direction * np.pi / 2) # x coordinate center of circle
97 | CenterY = PointAndTangent[i-1, 1] \
98 | + np.abs(r) * np.sin(ang + direction * np.pi / 2) # y coordinate center of circle
99 |
100 | spanAng = l / np.abs(r) # Angle spanned by the circle
101 | psi = wrap(ang + spanAng * np.sign(r)) # Angle of the tangent vector at the last point of the segment
102 |
103 | angleNormal = wrap((direction * np.pi / 2 + ang))
104 | angle = -(np.pi - np.abs(angleNormal)) * (sign(angleNormal))
105 | x = CenterX + np.abs(r) * np.cos(
106 | angle + direction * spanAng) # x coordinate of the last point of the segment
107 | y = CenterY + np.abs(r) * np.sin(
108 | angle + direction * spanAng) # y coordinate of the last point of the segment
109 |
110 | if i == 0:
111 | NewLine = np.array([x, y, psi, PointAndTangent[i, 3], l, 1 / r])
112 | else:
113 | NewLine = np.array([x, y, psi, PointAndTangent[i-1, 3] + PointAndTangent[i-1, 4], l, 1 / r])
114 |
115 | PointAndTangent[i, :] = NewLine # Write the new info
116 | # plt.plot(x, y, 'or')
117 |
118 |
119 | xs = PointAndTangent[-2, 0]
120 | ys = PointAndTangent[-2, 1]
121 | xf = 0
122 | yf = 0
123 | psif = 0
124 |
125 | # plt.plot(xf, yf, 'or')
126 | # plt.show()
127 | l = np.sqrt((xf - xs) ** 2 + (yf - ys) ** 2)
128 |
129 | NewLine = np.array([xf, yf, psif, PointAndTangent[-2, 3] + PointAndTangent[-2, 4], l, 0])
130 | PointAndTangent[-1, :] = NewLine
131 |
132 | self.PointAndTangent = PointAndTangent
133 | self.TrackLength = PointAndTangent[-1, 3] + PointAndTangent[-1, 4]
134 |
135 | def getGlobalPosition(self, s, ey):
136 | """coordinate transformation from curvilinear reference frame (e, ey) to inertial reference frame (X, Y)
137 | (s, ey): position in the curvilinear reference frame
138 | """
139 |
140 | # wrap s along the track
141 | while (s > self.TrackLength):
142 | s = s - self.TrackLength
143 |
144 | # Compute the segment in which system is evolving
145 | PointAndTangent = self.PointAndTangent
146 |
147 | index = np.all([[s >= PointAndTangent[:, 3]], [s < PointAndTangent[:, 3] + PointAndTangent[:, 4]]], axis=0)
148 | i = int(np.where(np.squeeze(index))[0])
149 |
150 | if PointAndTangent[i, 5] == 0.0: # If segment is a straight line
151 | # Extract the first final and initial point of the segment
152 | xf = PointAndTangent[i, 0]
153 | yf = PointAndTangent[i, 1]
154 | xs = PointAndTangent[i - 1, 0]
155 | ys = PointAndTangent[i - 1, 1]
156 | psi = PointAndTangent[i, 2]
157 |
158 | # Compute the segment length
159 | deltaL = PointAndTangent[i, 4]
160 | reltaL = s - PointAndTangent[i, 3]
161 |
162 | # Do the linear combination
163 | x = (1 - reltaL / deltaL) * xs + reltaL / deltaL * xf + ey * np.cos(psi + np.pi / 2)
164 | y = (1 - reltaL / deltaL) * ys + reltaL / deltaL * yf + ey * np.sin(psi + np.pi / 2)
165 | else:
166 | r = 1 / PointAndTangent[i, 5] # Extract curvature
167 | ang = PointAndTangent[i - 1, 2] # Extract angle of the tangent at the initial point (i-1)
168 | # Compute the center of the arc
169 | if r >= 0:
170 | direction = 1
171 | else:
172 | direction = -1
173 |
174 | CenterX = PointAndTangent[i - 1, 0] \
175 | + np.abs(r) * np.cos(ang + direction * np.pi / 2) # x coordinate center of circle
176 | CenterY = PointAndTangent[i - 1, 1] \
177 | + np.abs(r) * np.sin(ang + direction * np.pi / 2) # y coordinate center of circle
178 |
179 | spanAng = (s - PointAndTangent[i, 3]) / (np.pi * np.abs(r)) * np.pi
180 |
181 | angleNormal = wrap((direction * np.pi / 2 + ang))
182 | angle = -(np.pi - np.abs(angleNormal)) * (sign(angleNormal))
183 |
184 | x = CenterX + (np.abs(r) - direction * ey) * np.cos(
185 | angle + direction * spanAng) # x coordinate of the last point of the segment
186 | y = CenterY + (np.abs(r) - direction * ey) * np.sin(
187 | angle + direction * spanAng) # y coordinate of the last point of the segment
188 |
189 | return x, y
190 |
191 | def getLocalPosition(self, x, y, psi):
192 | """coordinate transformation from inertial reference frame (X, Y) to curvilinear reference frame (s, ey)
193 | (X, Y): position in the inertial reference frame
194 | """
195 | PointAndTangent = self.PointAndTangent
196 | CompletedFlag = 0
197 |
198 |
199 |
200 | for i in range(0, PointAndTangent.shape[0]):
201 | if CompletedFlag == 1:
202 | break
203 |
204 | if PointAndTangent[i, 5] == 0.0: # If segment is a straight line
205 | # Extract the first final and initial point of the segment
206 | xf = PointAndTangent[i, 0]
207 | yf = PointAndTangent[i, 1]
208 | xs = PointAndTangent[i - 1, 0]
209 | ys = PointAndTangent[i - 1, 1]
210 |
211 | psi_unwrap = np.unwrap([PointAndTangent[i - 1, 2], psi])[1]
212 | epsi = psi_unwrap - PointAndTangent[i - 1, 2]
213 | # Check if on the segment using angles
214 | if (la.norm(np.array([xs, ys]) - np.array([x, y]))) == 0:
215 | s = PointAndTangent[i, 3]
216 | ey = 0
217 | CompletedFlag = 1
218 |
219 | elif (la.norm(np.array([xf, yf]) - np.array([x, y]))) == 0:
220 | s = PointAndTangent[i, 3] + PointAndTangent[i, 4]
221 | ey = 0
222 | CompletedFlag = 1
223 | else:
224 | if np.abs(computeAngle( [x,y] , [xs, ys], [xf, yf])) <= np.pi/2 and np.abs(computeAngle( [x,y] , [xf, yf], [xs, ys])) <= np.pi/2:
225 | v1 = np.array([x,y]) - np.array([xs, ys])
226 | angle = computeAngle( [xf,yf] , [xs, ys], [x, y])
227 | s_local = la.norm(v1) * np.cos(angle)
228 | s = s_local + PointAndTangent[i, 3]
229 | ey = la.norm(v1) * np.sin(angle)
230 |
231 | if np.abs(ey)<= self.halfWidth + self.slack:
232 | CompletedFlag = 1
233 |
234 | else:
235 | xf = PointAndTangent[i, 0]
236 | yf = PointAndTangent[i, 1]
237 | xs = PointAndTangent[i - 1, 0]
238 | ys = PointAndTangent[i - 1, 1]
239 |
240 | r = 1 / PointAndTangent[i, 5] # Extract curvature
241 | if r >= 0:
242 | direction = 1
243 | else:
244 | direction = -1
245 |
246 | ang = PointAndTangent[i - 1, 2] # Extract angle of the tangent at the initial point (i-1)
247 |
248 | # Compute the center of the arc
249 | CenterX = xs + np.abs(r) * np.cos(ang + direction * np.pi / 2) # x coordinate center of circle
250 | CenterY = ys + np.abs(r) * np.sin(ang + direction * np.pi / 2) # y coordinate center of circle
251 |
252 | # Check if on the segment using angles
253 | if (la.norm(np.array([xs, ys]) - np.array([x, y]))) == 0:
254 | ey = 0
255 | psi_unwrap = np.unwrap([ang, psi])[1]
256 | epsi = psi_unwrap - ang
257 | s = PointAndTangent[i, 3]
258 | CompletedFlag = 1
259 | elif (la.norm(np.array([xf, yf]) - np.array([x, y]))) == 0:
260 | s = PointAndTangent[i, 3] + PointAndTangent[i, 4]
261 | ey = 0
262 | psi_unwrap = np.unwrap([PointAndTangent[i, 2], psi])[1]
263 | epsi = psi_unwrap - PointAndTangent[i, 2]
264 | CompletedFlag = 1
265 | else:
266 | arc1 = PointAndTangent[i, 4] * PointAndTangent[i, 5]
267 | arc2 = computeAngle([xs, ys], [CenterX, CenterY], [x, y])
268 | if np.sign(arc1) == np.sign(arc2) and np.abs(arc1) >= np.abs(arc2):
269 | v = np.array([x, y]) - np.array([CenterX, CenterY])
270 | s_local = np.abs(arc2)*np.abs(r)
271 | s = s_local + PointAndTangent[i, 3]
272 | ey = -np.sign(direction) * (la.norm(v) - np.abs(r))
273 | psi_unwrap = np.unwrap([ang + arc2, psi])[1]
274 | epsi = psi_unwrap - (ang + arc2)
275 |
276 | if np.abs(ey) <= self.halfWidth + self.slack:
277 | CompletedFlag = 1
278 |
279 | if epsi>1.0:
280 | pdb.set_trace()
281 |
282 | if CompletedFlag == 0:
283 | s = 10000
284 | ey = 10000
285 | epsi = 10000
286 |
287 | print("Error!! POINT OUT OF THE TRACK!!!! <==================")
288 | pdb.set_trace()
289 |
290 | return s, ey, epsi, CompletedFlag
291 |
292 | def curvature(self, s):
293 | """curvature computation
294 | s: curvilinear abscissa at which the curvature has to be evaluated
295 | PointAndTangent: points and tangent vectors defining the map (these quantities are initialized in the map object)
296 | """
297 | TrackLength = self.PointAndTangent[-1,3]+self.PointAndTangent[-1,4]
298 |
299 | # In case on a lap after the first one
300 | while (s > TrackLength):
301 | s = s - TrackLength
302 |
303 | # Given s \in [0, TrackLength] compute the curvature
304 | # Compute the segment in which system is evolving
305 | index = np.all([[s >= self.PointAndTangent[:, 3]], [s < self.PointAndTangent[:, 3] + self.PointAndTangent[:, 4]]], axis=0)
306 |
307 | i = int(np.where(np.squeeze(index))[0])
308 | curvature = self.PointAndTangent[i, 5]
309 |
310 | return curvature
311 |
312 | def getAngle(self, s, epsi):
313 | """TO DO
314 | """
315 | TrackLength = self.PointAndTangent[-1,3]+self.PointAndTangent[-1,4]
316 |
317 | # In case on a lap after the first one
318 | while (s > TrackLength):
319 | s = s - TrackLength
320 |
321 | # Given s \in [0, TrackLength] compute the curvature
322 | # Compute the segment in which system is evolving
323 | index = np.all([[s >= self.PointAndTangent[:, 3]], [s < self.PointAndTangent[:, 3] + self.PointAndTangent[:, 4]]], axis=0)
324 |
325 | i = int(np.where(np.squeeze(index))[0])
326 |
327 | if i > 0:
328 | ang = self.PointAndTangent[i - 1, 2]
329 | else:
330 | ang = 0
331 |
332 | if self.PointAndTangent[i, 5] == 0:
333 | r= 0
334 | else:
335 | r = 1 / self.PointAndTangent[i, 5] # Radius of curvature
336 |
337 | if r == 0:
338 | # On a straight part of the circuit
339 | angle_at_s = ang + epsi
340 | else:
341 | # On a curve
342 | cumulative_s = self.PointAndTangent[i, 3]
343 | relative_s = s - cumulative_s
344 | spanAng = relative_s / np.abs(r) # Angle spanned by the circle
345 | psi = wrap(ang + spanAng * np.sign(r)) # Angle of the tangent vector at the last point of the segment
346 | # pdb.set_trace()
347 | angle_at_s = psi + epsi
348 |
349 | return angle_at_s
350 |
351 | # ======================================================================================================================
352 | # ======================================================================================================================
353 | # ====================================== Internal utilities functions ==================================================
354 | # ======================================================================================================================
355 | # ======================================================================================================================
356 | def computeAngle(point1, origin, point2):
357 | # The orientation of this angle matches that of the coordinate system. Tha is why a minus sign is needed
358 | v1 = np.array(point1) - np.array(origin)
359 | v2 = np.array(point2) - np.array(origin)
360 |
361 | dot = v1[0] * v2[0] + v1[1] * v2[1] # dot product between [x1, y1] and [x2, y2]
362 | det = v1[0] * v2[1] - v1[1] * v2[0] # determinant
363 | angle = np.arctan2(det, dot) # atan2(y, x) or atan2(sin, cos)
364 |
365 | return angle # np.arctan2(sinang, cosang)
366 |
367 | def wrap(angle):
368 | if angle < -np.pi:
369 | w_angle = 2 * np.pi + angle
370 | elif angle > np.pi:
371 | w_angle = angle - 2 * np.pi
372 | else:
373 | w_angle = angle
374 |
375 | return w_angle
376 |
377 | def sign(a):
378 | if a >= 0:
379 | res = 1
380 | else:
381 | res = -1
382 |
383 | return res
--------------------------------------------------------------------------------
/src/initControllerParameters.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from PredictiveControllers import MPC, LMPC, MPCParams
3 |
4 | def initMPCParams(n, d, N, vt):
5 | # Buil the matrices for the state constraint in each region. In the region i we want Fx[i]x <= bx[b]
6 | Fx = np.array([[0., 0., 0., 0., 0., 1.],
7 | [0., 0., 0., 0., 0., -1.]])
8 |
9 | bx = np.array([[2.], # max ey
10 | [2.]]), # max ey
11 |
12 | Fu = np.kron(np.eye(2), np.array([1, -1])).T
13 | bu = np.array([[0.5], # -Min Steering
14 | [0.5], # Max Steering
15 | [10.0], # -Min Acceleration
16 | [10.0]]) # Max Acceleration
17 |
18 | # Tuning Parameters
19 | Q = np.diag([1.0, 1.0, 1, 1, 0.0, 100.0]) # vx, vy, wz, epsi, s, ey
20 | R = np.diag([1.0, 10.0]) # delta, a
21 | xRef = np.array([vt, 0, 0, 0, 0, 0])
22 | Qslack = 1 * np.array([0, 50])
23 |
24 | mpcParameters = MPCParams(n=n, d=d, N=N, Q=Q, R=R, Fx=Fx, bx=bx, Fu=Fu, bu=bu, xRef=xRef, slacks=True, Qslack=Qslack)
25 | mpcParametersLTV = MPCParams(n=n, d=d, N=N, Q=Q, R=R, Fx=Fx, bx=bx, Fu=Fu, bu=bu, xRef=xRef, slacks=True, Qslack=Qslack)
26 | return mpcParameters, mpcParametersLTV
27 |
28 | def initLMPCParams(map, N):
29 | # Buil the matrices for the state constraint in each region. In the region i we want Fx[i]x <= bx[b]
30 | Fx = np.array([[0., 0., 0., 0., 0., 1.],
31 | [0., 0., 0., 0., 0., -1.]])
32 |
33 | bx = np.array([[map.halfWidth], # max ey
34 | [map.halfWidth]]), # max ey
35 |
36 | Fu = np.kron(np.eye(2), np.array([1, -1])).T
37 | bu = np.array([[0.5], # -Min Steering
38 | [0.5], # Max Steering
39 | [10.0], # -Min Acceleration
40 | [10.0]]) # Max Acceleration
41 |
42 | # Safe Set Parameters
43 | numSS_it = 4 # Number of trajectories used at each iteration to build the safe set
44 | numSS_Points = 12*numSS_it # Number of points to select from each trajectory to build the safe set
45 |
46 | Laps = 40+numSS_it # Total LMPC laps
47 | TimeLMPC = 400 # Simulation time
48 |
49 | # Tuning Parameters
50 | QterminalSlack = 500 * np.diag([1, 1, 1, 1, 1, 1]) # Cost on the slack variable for the terminal constraint
51 | Qslack = 1 * np.array([5, 25]) # Quadratic and linear slack lane cost
52 | Q_LMPC = 0 * np.diag([0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) # State cost x = [vx, vy, wz, epsi, s, ey]
53 | R_LMPC = 0 * np.diag([1.0, 1.0]) # Input cost u = [delta, a]
54 | dR_LMPC = 5 * np.array([1.0, 10.0]) # Input rate cost u
55 | n = Q_LMPC.shape[0]
56 | d = R_LMPC.shape[0]
57 |
58 | lmpcParameters = MPCParams(n=n, d=d, N=N, Q=Q_LMPC, R=R_LMPC, dR=dR_LMPC, Fx=Fx, bx=bx, Fu=Fu, bu=bu, slacks=True, Qslack=Qslack)
59 | return numSS_it, numSS_Points, Laps, TimeLMPC, QterminalSlack, lmpcParameters
--------------------------------------------------------------------------------
/src/main.py:
--------------------------------------------------------------------------------
1 | # ----------------------------------------------------------------------------------------------------------------------
2 | # Licensing Information: You are free to use or extend these projects for
3 | # education or reserach purposes provided that you provide clear attribution to UC Berkeley,
4 | # including a reference to the papers describing the control framework:
5 | # [1] Ugo Rosolia and Francesco Borrelli. "Learning Model Predictive Control for Iterative Tasks. A Data-Driven
6 | # Control Framework." In IEEE Transactions on Automatic Control (2017).
7 | #
8 | # [2] Ugo Rosolia and Francesco Borrelli "Learning how to autonomously race a car: a predictive control approach"
9 | # In 2017 IEEE Conference on Decision and Control (CDC)
10 | #
11 | # [3] Ugo Rosolia and Francesco Borrelli. "Learning Model Predictive Control for Iterative Tasks: A Computationally
12 | # IEEE Transactions on Control Systems Technology (2019).
13 | #
14 | # Attibution Information: Code developed by Ugo Rosolia
15 | # (for clarifiactions and suggestions please write to ugo.rosolia@berkeley.edu).
16 | #
17 | # Code description: Simulation of the Learning Model Predictive Controller (LMPC). The main file runs:
18 | # 1) A PID path following controller
19 | # 2) A Model Predictive Controller (MPC) which uses a LTI model identified from the data collected with the PID in 1)
20 | # 3) A MPC which uses a LTV model identified from the date collected in 1)
21 | # 4) A LMPC for racing where the safe set and value function approximation are build using the data from 1), 2) and 3)
22 | # ----------------------------------------------------------------------------------------------------------------------
23 | import sys
24 | sys.path.append('fnc/simulator')
25 | sys.path.append('fnc/controller')
26 | sys.path.append('fnc')
27 | import matplotlib.pyplot as plt
28 | from plot import plotTrajectory, plotClosedLoopLMPC, animation_xy, animation_states, saveGif_xyResults
29 | from initControllerParameters import initMPCParams, initLMPCParams
30 | from PredictiveControllers import MPC, LMPC, MPCParams
31 | from PredictiveModel import PredictiveModel
32 | from Utilities import Regression, PID
33 | from SysModel import Simulator
34 | from Track import Map
35 | import numpy as np
36 | import pickle
37 | import pdb
38 |
39 | def main():
40 | # ======================================================================================================================
41 | # ============================================= Initialize parameters =================================================
42 | # ======================================================================================================================
43 | N = 14 # Horizon length
44 | n = 6; d = 2 # State and Input dimension
45 | x0 = np.array([0.5, 0, 0, 0, 0, 0]) # Initial condition
46 | xS = [x0, x0]
47 | dt = 0.1
48 |
49 | map = Map(0.4) # Initialize map
50 | vt = 0.8 # target vevlocity
51 |
52 | # Initialize controller parameters
53 | mpcParam, ltvmpcParam = initMPCParams(n, d, N, vt)
54 | numSS_it, numSS_Points, Laps, TimeLMPC, QterminalSlack, lmpcParameters = initLMPCParams(map, N)
55 |
56 | # Init simulators
57 | simulator = Simulator(map)
58 | LMPCsimulator = Simulator(map, multiLap = False, flagLMPC = True)
59 |
60 | # ======================================================================================================================
61 | # ======================================= PID path following ===========================================================
62 | # ======================================================================================================================
63 | print("Starting PID")
64 | # Initialize pid and run sim
65 | PIDController = PID(vt)
66 | xPID_cl, uPID_cl, xPID_cl_glob, _ = simulator.sim(xS, PIDController)
67 | print("===== PID terminated")
68 |
69 | # ======================================================================================================================
70 | # ====================================== LINEAR REGRESSION ============================================================
71 | # ======================================================================================================================
72 | print("Starting MPC")
73 | # Estimate system dynamics
74 | lamb = 0.0000001
75 | A, B, Error = Regression(xPID_cl, uPID_cl, lamb)
76 | mpcParam.A = A
77 | mpcParam.B = B
78 | # Initialize MPC and run closed-loop sim
79 | mpc = MPC(mpcParam)
80 | xMPC_cl, uMPC_cl, xMPC_cl_glob, _ = simulator.sim(xS, mpc)
81 | print("===== MPC terminated")
82 |
83 | # ======================================================================================================================
84 | # =================================== LOCAL LINEAR REGRESSION =========================================================
85 | # ======================================================================================================================
86 | print("Starting TV-MPC")
87 | # Initialized predictive model
88 | predictiveModel = PredictiveModel(n, d, map, 1)
89 | predictiveModel.addTrajectory(xPID_cl,uPID_cl)
90 | #Initialize TV-MPC
91 | ltvmpcParam.timeVarying = True
92 | mpc = MPC(ltvmpcParam, predictiveModel)
93 | # Run closed-loop sim
94 | xTVMPC_cl, uTVMPC_cl, xTVMPC_cl_glob, _ = simulator.sim(xS, mpc)
95 | print("===== TV-MPC terminated")
96 |
97 | # ======================================================================================================================
98 | # ============================== LMPC w\ LOCAL LINEAR REGRESSION ======================================================
99 | # ======================================================================================================================
100 | print("Starting LMPC")
101 | # Initialize Predictive Model for lmpc
102 | lmpcpredictiveModel = PredictiveModel(n, d, map, 4)
103 | for i in range(0,4): # add trajectories used for model learning
104 | lmpcpredictiveModel.addTrajectory(xPID_cl,uPID_cl)
105 |
106 | # Initialize Controller
107 | lmpcParameters.timeVarying = True
108 | lmpc = LMPC(numSS_Points, numSS_it, QterminalSlack, lmpcParameters, lmpcpredictiveModel)
109 | for i in range(0,4): # add trajectories for safe set
110 | lmpc.addTrajectory( xPID_cl, uPID_cl, xPID_cl_glob)
111 |
112 | # Run sevaral laps
113 | for it in range(numSS_it, Laps):
114 | # Simulate controller
115 | xLMPC, uLMPC, xLMPC_glob, xS = LMPCsimulator.sim(xS, lmpc)
116 | # Add trajectory to controller
117 | lmpc.addTrajectory( xLMPC, uLMPC, xLMPC_glob)
118 | # lmpcpredictiveModel.addTrajectory(np.append(xLMPC,np.array([xS[0]]),0),np.append(uLMPC, np.zeros((1,2)),0))
119 | lmpcpredictiveModel.addTrajectory(xLMPC,uLMPC)
120 | print("Completed lap: ", it, " in ", np.round(lmpc.Qfun[it][0]*dt, 2)," seconds")
121 | print("===== LMPC terminated")
122 |
123 | # # ======================================================================================================================
124 | # # ========================================= PLOT TRACK =================================================================
125 | # # ======================================================================================================================
126 | for i in range(0, lmpc.it):
127 | print("Lap time at iteration ", i, " is ",np.round( lmpc.Qfun[i][0]*dt, 2), "s")
128 |
129 | print("===== Start Plotting")
130 | plotTrajectory(map, xPID_cl, xPID_cl_glob, uPID_cl, 'PID')
131 | plotTrajectory(map, xMPC_cl, xMPC_cl_glob, uMPC_cl, 'MPC')
132 | plotTrajectory(map, xTVMPC_cl, xTVMPC_cl_glob, uTVMPC_cl, 'TV-MPC')
133 | plotClosedLoopLMPC(lmpc, map)
134 | animation_xy(map, lmpc, Laps-1)
135 | plt.show()
136 |
137 | # animation_states(map, LMPCOpenLoopData, lmpc, Laps-2)
138 | # animation_states(map, LMPCOpenLoopData, lmpc, Laps-2)
139 | # animation_states(map, LMPCOpenLoopData, lmpc, Laps-2)
140 | # animation_states(map, LMPCOpenLoopData, lmpc, Laps-2)
141 | # saveGif_xyResults(map, LMPCOpenLoopData, lmpc, Laps-2)
142 |
143 | if __name__== "__main__":
144 | main()
--------------------------------------------------------------------------------