quadrotor-ocp.py
Go to the documentation of this file.
1 import sys
2 from pathlib import Path
3 
4 import casadi
5 import numpy as np
6 import pinocchio as pin
7 import pinocchio.casadi as cpin
8 
9 # This use the example-robot-data submodule, but if you have it already properly
10 # installed in your PYTHONPATH, there is no need for this sys.path thing
11 path = Path(__file__).parent.parent.parent / "models" / "example-robot-data" / "python"
12 sys.path.append(str(path))
13 import example_robot_data # noqa: E402
14 
15 # Problem parameters
16 x_goal = [1, 0, 1.5, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
17 x0 = [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
18 nodes = 80
19 dt = 0.02
20 
21 # Quadcopter parameters
22 d_cog, cf, cm = 0.1525, 6.6e-5, 1e-6
23 
24 tau_f = np.array(
25  [
26  [0.0, 0.0, 0.0, 0.0],
27  [0.0, 0.0, 0.0, 0.0],
28  [1.0, 1.0, 1.0, 1.0],
29  [0.0, d_cog, 0.0, -d_cog],
30  [-d_cog, 0.0, d_cog, 0.0],
31  [-cm / cf, cm / cf, -cm / cf, cm / cf],
32  ]
33 )
34 
35 # Other variables
36 x_nom = [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
37 
38 
40  u = casadi.SX.sym("u", 4) # rotor velocities
41  tau = tau_f @ u
42 
43  return casadi.Function("act_model", [u], [tau], ["u"], ["tau"])
44 
45 
46 def state_integrate(model):
47  q = casadi.SX.sym("dq", model.nq)
48  dq = casadi.SX.sym("q", model.nv)
49  v = casadi.SX.sym("v", model.nv)
50  dv = casadi.SX.sym("dv", model.nv)
51 
52  q_next = cpin.integrate(model, q, dq)
53  v_next = v + dv
54 
55  dx = casadi.vertcat(dq, dv)
56  x = casadi.vertcat(q, v)
57  x_next = casadi.vertcat(q_next, v_next)
58 
59  return casadi.Function("integrate", [x, dx], [x_next], ["x", "dx"], ["x_next"])
60 
61 
62 def state_difference(model):
63  q0 = casadi.SX.sym("q0", model.nq)
64  q1 = casadi.SX.sym("q1", model.nq)
65  v0 = casadi.SX.sym("v0", model.nv)
66  v1 = casadi.SX.sym("v1", model.nv)
67 
68  q_diff = cpin.difference(model, q0, q1)
69  v_diff = v1 - v0
70 
71  x0 = casadi.vertcat(q0, v0)
72  x1 = casadi.vertcat(q1, v1)
73  x_diff = casadi.vertcat(q_diff, v_diff)
74 
75  return casadi.Function("difference", [x0, x1], [x_diff], ["x0", "x1"], ["x_diff"])
76 
77 
78 def euler_integration(model, data, dt):
79  nu = 4
80  u = casadi.SX.sym("u", nu)
81 
82  # tau = casadi.vertcat(np.zeros(model.nv - nu), u)
83  tau = actuation_model()(u)
84 
85  q = casadi.SX.sym("q", model.nq)
86  v = casadi.SX.sym("v", model.nv)
87 
88  a = cpin.aba(model, data, q, v, tau)
89 
90  dq = v * dt + a * dt**2
91  dv = a * dt
92 
93  x = casadi.vertcat(q, v)
94  dx = casadi.vertcat(dq, dv)
95  x_next = state_integrate(model)(x, dx)
96 
97  return casadi.Function("int_dyn", [x, u], [x_next], ["x", "u"], ["x_next"])
98 
99 
101  dx = casadi.SX.sym("dx", model.nv * 2)
102 
103  x_N = state_integrate(model)(x_nom, dx)
104  e_goal = state_difference(model)(x_N, x_goal)
105 
106  cost = 0.5 * e_goal.T @ e_goal
107 
108  return casadi.Function("quad_cost", [dx], [cost], ["dx"], ["cost"])
109 
110 
112  def __init__(self, model, terminal_soft_constraint=False):
113  self.opti = casadi.Opti()
114 
115  self.model = model
116  self.data = self.model.createData()
117 
118  self.c_model = cpin.Model(self.model)
119  self.c_data = self.c_model.createData()
120 
121  nv = self.c_model.nv
122  nu = 4
123 
124  self.c_dxs = self.opti.variable(2 * nv, nodes + 1) # state trajectory
125  self.c_us = self.opti.variable(nu, nodes) # control trajectory
126 
127  # Objective function
128  obj = 0
129 
130  # State & Control regularization
131  for i in range(nodes):
132  x_i = state_integrate(self.c_model)(x_nom, self.c_dxs[:, i])
133  e_reg = state_difference(self.c_model)(x_nom, x_i)
134  obj += (
135  1e-5 * 0.5 * e_reg.T @ e_reg
136  + 1e-5 * 0.5 * self.c_us[:, i].T @ self.c_us[:, i]
137  )
138  if terminal_soft_constraint:
139  obj += 1000 * cost_quadratic_state_error(self.c_model)(self.c_dxs[:, nodes])
140 
141  self.opti.minimize(obj)
142 
143  # Dynamical constraints
144  for i in range(nodes):
145  x_i = state_integrate(self.c_model)(x_nom, self.c_dxs[:, i])
146  x_i_1 = state_integrate(self.c_model)(x_nom, self.c_dxs[:, i + 1])
147  f_x_u = euler_integration(self.c_model, self.c_data, dt)(
148  x_i, self.c_us[:, i]
149  )
150  gap = state_difference(self.c_model)(f_x_u, x_i_1)
151 
152  self.opti.subject_to(gap == [0] * 12)
153 
154  # Control constraints
155  self.opti.subject_to(self.opti.bounded(0, self.c_us, 5))
156 
157  # Final constraint
158  if not terminal_soft_constraint:
159  x_N = state_integrate(self.c_model)(x_nom, self.c_dxs[:, nodes])
160  e_goal = state_difference(self.c_model)(x_N, x_goal)
161  self.opti.subject_to(e_goal == [0] * 12)
162 
163  # Initial state
164  x_0 = state_integrate(self.c_model)(x_nom, self.c_dxs[:, 0])
165  self.opti.subject_to(state_difference(self.c_model)(x0, x_0) == [0] * 12)
166 
167  # Warm start
168  self.opti.set_initial(
169  self.c_dxs, np.vstack([np.zeros(12) for _ in range(nodes + 1)]).T
170  )
171  self.opti.set_initial(
172  self.c_us, np.vstack([np.zeros(4) for _ in range(nodes)]).T
173  )
174 
175  def solve(self, approx_hessian=True):
176  opts = {"verbose": False}
177  opts["ipopt"] = {
178  "max_iter": 1000,
179  "linear_solver": "mumps",
180  "tol": 3.82e-6,
181  "mu_strategy": "adaptive",
182  }
183 
184  if approx_hessian:
185  opts["ipopt"]["hessian_approximation"] = "limited-memory"
186 
187  # Solver initialization
188  self.opti.solver("ipopt", opts) # set numerical backend
189 
190  try:
191  self.sol = self.opti.solve()
192  except: # noqa: E722
193  self.sol = self.opti.debug
194 
195  if self.sol.stats()["return_status"] == "Solve_Succeeded":
196  self._retract_trajectory()
197  self._compute_gaps()
198 
200  self.xs = []
201  self.us = []
202  self.gaps = []
203 
204  nq = self.model.nq
205  nv = self.model.nv
206 
207  for idx, (dx_sol, u_sol) in enumerate(
208  zip(self.sol.value(self.c_dxs).T, self.sol.value(self.c_us).T)
209  ):
210  q = pin.integrate(self.model, np.array(x_nom)[:nq], dx_sol[:nv])
211  v = dx_sol[nv:]
212 
213  self.xs.append(np.concatenate([q, v]))
214  self.us.append(u_sol)
215 
216  q = pin.integrate(
217  self.model, np.array(x_nom)[:nq], self.sol.value(self.c_dxs).T[nodes, :nv]
218  )
219  v = self.sol.value(self.c_dxs).T[nodes, nv:]
220  self.xs.append(np.concatenate([q, v]))
221 
222  def _compute_gaps(self):
223  self.gaps = {"vector": [np.zeros(self.model.nv * 2)], "norm": [0]}
224 
225  nq = self.model.nq
226  _nv = self.model.nv
227 
228  for idx, (x, u) in enumerate(zip(self.xs, self.us)):
229  x_pin = self._simulate_step(x, u)
230 
231  gap_q = pin.difference(self.model, x_pin[:nq], self.xs[idx + 1][:nq])
232  gap_v = self.xs[idx + 1][nq:] - x_pin[nq:]
233 
234  gap = np.concatenate([gap_q, gap_v])
235  self.gaps["vector"].append(gap)
236  self.gaps["norm"].append(np.linalg.norm(gap))
237 
238  def _simulate_step(self, x, u):
239  nq = self.model.nq
240  _nv = self.model.nv
241 
242  q = x[:nq]
243  v = x[nq:]
244 
245  tau = tau_f @ u
246 
247  a = pin.aba(self.model, self.data, q, v, tau)
248 
249  dq = v * dt + a * dt**2
250  dv = a * dt
251 
252  q_next = pin.integrate(self.model, q, dq)
253  v_next = v + dv
254 
255  x_next = np.concatenate([q_next, v_next])
256 
257  return x_next
258 
259 
260 def main():
261  robot = example_robot_data.load("hector")
262  model = robot.model
263 
264  oc_problem = OptimalControlProblem(model, terminal_soft_constraint=False)
265 
266  oc_problem.solve(approx_hessian=True)
267 
268  # --------------PLOTS-----------
269  try:
270  import matplotlib.pyplot as plt
271 
272  _, axs0 = plt.subplots(nrows=2)
273 
274  xs = np.vstack(oc_problem.xs)
275  axs0[0].plot(xs[:, :3])
276  axs0[0].set_title("Quadcopter position")
277 
278  axs0[1].plot(oc_problem.gaps["norm"])
279  axs0[1].set_title("Multiple shooting node gaps")
280 
281  _, axs1 = plt.subplots(nrows=4)
282  us = np.vstack(oc_problem.us)
283 
284  for idx, ax in enumerate(axs1):
285  ax.plot(us[:, idx])
286 
287  plt.show(block=False)
288  except ImportError as err:
289  print(
290  "Error while initializing the viewer. "
291  "It seems you should install Python meshcat"
292  )
293  print(err)
294  sys.exit(0)
295 
296 
297 if __name__ == "__main__":
298  main()
quadrotor-ocp.OptimalControlProblem.us
us
Definition: quadrotor-ocp.py:201
quadrotor-ocp.OptimalControlProblem.c_model
c_model
Definition: quadrotor-ocp.py:118
quadrotor-ocp.OptimalControlProblem.gaps
gaps
Definition: quadrotor-ocp.py:202
quadrotor-ocp.euler_integration
def euler_integration(model, data, dt)
Definition: quadrotor-ocp.py:78
quadrotor-ocp.OptimalControlProblem.model
model
Definition: quadrotor-ocp.py:115
quadrotor-ocp.OptimalControlProblem.solve
def solve(self, approx_hessian=True)
Definition: quadrotor-ocp.py:175
quadrotor-ocp.OptimalControlProblem._compute_gaps
def _compute_gaps(self)
Definition: quadrotor-ocp.py:222
pinocchio.casadi
Definition: bindings/python/pinocchio/casadi/__init__.py:1
quadrotor-ocp.OptimalControlProblem.opti
opti
Definition: quadrotor-ocp.py:113
quadrotor-ocp.state_integrate
def state_integrate(model)
Definition: quadrotor-ocp.py:46
quadrotor-ocp.state_difference
def state_difference(model)
Definition: quadrotor-ocp.py:62
quadrotor-ocp.actuation_model
def actuation_model()
Definition: quadrotor-ocp.py:39
quadrotor-ocp.cost_quadratic_state_error
def cost_quadratic_state_error(model)
Definition: quadrotor-ocp.py:100
quadrotor-ocp.OptimalControlProblem.c_us
c_us
Definition: quadrotor-ocp.py:125
quadrotor-ocp.OptimalControlProblem.__init__
def __init__(self, model, terminal_soft_constraint=False)
Definition: quadrotor-ocp.py:112
pinocchio::createData
ConstraintDataTpl< Scalar, Options, ConstraintCollectionTpl > createData(const ConstraintModelTpl< Scalar, Options, ConstraintCollectionTpl > &cmodel)
Definition: constraint-model-visitor.hpp:239
quadrotor-ocp.OptimalControlProblem.c_dxs
c_dxs
Definition: quadrotor-ocp.py:124
quadrotor-ocp.OptimalControlProblem._retract_trajectory
def _retract_trajectory(self)
Definition: quadrotor-ocp.py:199
quadrotor-ocp.OptimalControlProblem.sol
sol
Definition: quadrotor-ocp.py:191
quadrotor-ocp.OptimalControlProblem.c_data
c_data
Definition: quadrotor-ocp.py:119
quadrotor-ocp.OptimalControlProblem.data
data
Definition: quadrotor-ocp.py:116
quadrotor-ocp.OptimalControlProblem
Definition: quadrotor-ocp.py:111
boost::fusion::append
result_of::push_front< V const, T >::type append(T const &t, V const &v)
Append the element T at the front of boost fusion vector V.
Definition: fusion.hpp:32
quadrotor-ocp.OptimalControlProblem.xs
xs
Definition: quadrotor-ocp.py:200
quadrotor-ocp.main
def main()
Definition: quadrotor-ocp.py:260
quadrotor-ocp.OptimalControlProblem._simulate_step
def _simulate_step(self, x, u)
Definition: quadrotor-ocp.py:238


pinocchio
Author(s):
autogenerated on Fri Nov 1 2024 02:41:48