cbfpy 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cbfpy/__init__.py +11 -0
- cbfpy/cbfs/__init__.py +0 -0
- cbfpy/cbfs/cbf.py +384 -0
- cbfpy/cbfs/clf_cbf.py +490 -0
- cbfpy/config/__init__.py +0 -0
- cbfpy/config/cbf_config.py +401 -0
- cbfpy/config/clf_cbf_config.py +251 -0
- cbfpy/envs/__init__.py +0 -0
- cbfpy/envs/arm_envs.py +84 -0
- cbfpy/envs/base_env.py +69 -0
- cbfpy/envs/car_env.py +332 -0
- cbfpy/envs/drone_env.py +153 -0
- cbfpy/envs/point_robot_envs.py +209 -0
- cbfpy/examples/__init__.py +0 -0
- cbfpy/examples/adaptive_cruise_control_demo.py +117 -0
- cbfpy/examples/drone_demo.py +109 -0
- cbfpy/examples/joint_limits_demo.py +150 -0
- cbfpy/examples/point_robot_demo.py +91 -0
- cbfpy/examples/point_robot_obstacle_demo.py +118 -0
- cbfpy/temp/test_import.py +3 -0
- cbfpy/utils/__init__.py +0 -0
- cbfpy/utils/general_utils.py +131 -0
- cbfpy/utils/jax_utils.py +26 -0
- cbfpy/utils/math_utils.py +21 -0
- cbfpy/utils/visualization.py +93 -0
- cbfpy-0.0.1.dist-info/LICENSE +21 -0
- cbfpy-0.0.1.dist-info/METADATA +226 -0
- cbfpy-0.0.1.dist-info/RECORD +33 -0
- cbfpy-0.0.1.dist-info/WHEEL +5 -0
- cbfpy-0.0.1.dist-info/top_level.txt +2 -0
- test/__init__.py +0 -0
- test/test_speed.py +191 -0
- test/test_utils.py +34 -0
cbfpy/cbfs/clf_cbf.py
ADDED
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
"""
|
|
2
|
+
# Control Lyapunov Function / Control Barrier Functions (CLF-CBFs)
|
|
3
|
+
|
|
4
|
+
Whereas a CBF acts as a safety filter on top of a nominal controller, a CLF-CBF acts as a safe controller itself,
|
|
5
|
+
based on a control objective defined by the CLF and a safety constraint defined by the CBF. Note that the CLF
|
|
6
|
+
objective should be quadratic and positive-definite to fit in this QP framework.
|
|
7
|
+
|
|
8
|
+
The CLF-CBF optimizes the following:
|
|
9
|
+
```
|
|
10
|
+
minimize ||u||_{2}^{2} # CLF Objective (Example)
|
|
11
|
+
subject to LfV(z) + LgV(z)u <= -gamma(V(z)) # CLF Constraint
|
|
12
|
+
Lfh(z) + Lgh(z)u >= -alpha(h(z)) # CBF Constraint
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
As with the CBF, if this is a relative-degree-2 system, we update the constraints:
|
|
16
|
+
```
|
|
17
|
+
minimize ||u||_{2}^{2} # CLF Objective (Example)
|
|
18
|
+
subject to LfV_2(z) + LgV_2(z)u <= -gamma_2(V_2(z)) # RD2 CLF Constraint
|
|
19
|
+
Lfh_2(z) + Lgh_2(z)u >= -alpha_2(h_2(z)) # RD2 CBF Constraint
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
If there are constraints on the control input, we also enforce another constraint:
|
|
23
|
+
```
|
|
24
|
+
u_min <= u <= u_max # Control constraint
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
However, in general the CLF constraint and the CBF constraint cannot be strictly enforced together. We then
|
|
28
|
+
need to introduce a slack variable to relax the CLF constraint, ensuring that the CBF safety condition takes
|
|
29
|
+
priority over the CLF objective.
|
|
30
|
+
|
|
31
|
+
The optimization problem then becomes:
|
|
32
|
+
```
|
|
33
|
+
minimize ||u||_{2}^{2} + p * delta^2 # CLF Objective (Example)
|
|
34
|
+
subject to LfV(z) + LgV(z)u <= -gamma(V(z)) + delta # CLF Constraint
|
|
35
|
+
Lfh(z) + Lgh(z)u >= -alpha(h(z)) # CBF Constraint
|
|
36
|
+
```
|
|
37
|
+
where `p` is a large constant and `delta` is the slack variable.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
from typing import Tuple, Callable, Optional
|
|
41
|
+
|
|
42
|
+
import jax
|
|
43
|
+
import jax.numpy as jnp
|
|
44
|
+
from jax import Array
|
|
45
|
+
from jax.typing import ArrayLike
|
|
46
|
+
import qpax
|
|
47
|
+
|
|
48
|
+
from cbfpy.config.clf_cbf_config import CLFCBFConfig
|
|
49
|
+
from cbfpy.utils.jax_utils import conditional_jit
|
|
50
|
+
from cbfpy.utils.general_utils import print_warning
|
|
51
|
+
|
|
52
|
+
# Debugging flags to disable jit in specific sections of the code.
|
|
53
|
+
# Note: If any higher-level jits exist, those must also be set to debug (disable jit)
|
|
54
|
+
DEBUG_CONTROLLER = False
|
|
55
|
+
DEBUG_QP_DATA = False
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@jax.tree_util.register_static
|
|
59
|
+
class CLFCBF:
|
|
60
|
+
"""Control Lyapunov Function / Control Barrier Function (CLF-CBF) class.
|
|
61
|
+
|
|
62
|
+
The main constructor for this class is via the `from_config` method, which constructs a CLF-CBF instance
|
|
63
|
+
based on the provided CLFCBFConfig configuration object.
|
|
64
|
+
|
|
65
|
+
You can then use the CLF-CBF's `controller` method to compute the optimal control input
|
|
66
|
+
|
|
67
|
+
Examples:
|
|
68
|
+
```
|
|
69
|
+
# Construct a CLFCBFConfig for your problem
|
|
70
|
+
config = DroneConfig()
|
|
71
|
+
# Construct a CBF instance based on the config
|
|
72
|
+
clf_cbf = CLFCBF.from_config(config)
|
|
73
|
+
# Compute the safe control input
|
|
74
|
+
safe_control = clf_cbf.controller(current_state, desired_state)
|
|
75
|
+
```
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
# NOTE: The __init__ method is not used to construct a CLFCBF instance. Instead, use the `from_config` method.
|
|
79
|
+
# This is because Jax prefers for the __init__ method to not contain any input validation, so we do this
|
|
80
|
+
# in the CLFCBFConfig class instead.
|
|
81
|
+
def __init__(
|
|
82
|
+
self,
|
|
83
|
+
n: int,
|
|
84
|
+
m: int,
|
|
85
|
+
num_cbf: int,
|
|
86
|
+
num_clf: int,
|
|
87
|
+
u_min: Optional[tuple],
|
|
88
|
+
u_max: Optional[tuple],
|
|
89
|
+
control_constrained: bool,
|
|
90
|
+
relax_cbf: bool,
|
|
91
|
+
cbf_relaxation_penalty: float,
|
|
92
|
+
clf_relaxation_penalty: float,
|
|
93
|
+
h_1: Callable[[ArrayLike], Array],
|
|
94
|
+
h_2: Callable[[ArrayLike], Array],
|
|
95
|
+
f: Callable[[ArrayLike], Array],
|
|
96
|
+
g: Callable[[ArrayLike], Array],
|
|
97
|
+
alpha: Callable[[ArrayLike], Array],
|
|
98
|
+
alpha_2: Callable[[ArrayLike], Array],
|
|
99
|
+
V_1: Callable[[ArrayLike], Array],
|
|
100
|
+
V_2: Callable[[ArrayLike], Array],
|
|
101
|
+
gamma: Callable[[ArrayLike], Array],
|
|
102
|
+
gamma_2: Callable[[ArrayLike], Array],
|
|
103
|
+
H: Callable[[ArrayLike], Array],
|
|
104
|
+
F: Callable[[ArrayLike], Array],
|
|
105
|
+
solver_tol: float,
|
|
106
|
+
):
|
|
107
|
+
self.n = n
|
|
108
|
+
self.m = m
|
|
109
|
+
self.num_cbf = num_cbf
|
|
110
|
+
self.num_clf = num_clf
|
|
111
|
+
self.u_min = u_min
|
|
112
|
+
self.u_max = u_max
|
|
113
|
+
self.control_constrained = control_constrained
|
|
114
|
+
self.relax_cbf = relax_cbf
|
|
115
|
+
self.cbf_relaxation_penalty = cbf_relaxation_penalty
|
|
116
|
+
self.clf_relaxation_penalty = clf_relaxation_penalty
|
|
117
|
+
self.h_1 = h_1
|
|
118
|
+
self.h_2 = h_2
|
|
119
|
+
self.f = f
|
|
120
|
+
self.g = g
|
|
121
|
+
self.alpha = alpha
|
|
122
|
+
self.alpha_2 = alpha_2
|
|
123
|
+
self.V_1 = V_1
|
|
124
|
+
self.V_2 = V_2
|
|
125
|
+
self.gamma = gamma
|
|
126
|
+
self.gamma_2 = gamma_2
|
|
127
|
+
self.H = H
|
|
128
|
+
self.F = F
|
|
129
|
+
self.solver_tol = solver_tol
|
|
130
|
+
if relax_cbf:
|
|
131
|
+
self.qp_solver: Callable = jax.jit(qpax.solve_qp_elastic)
|
|
132
|
+
else:
|
|
133
|
+
self.qp_solver: Callable = jax.jit(qpax.solve_qp)
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
def from_config(cls, config: CLFCBFConfig) -> "CLFCBF":
|
|
137
|
+
"""Construct a CLF-CBF based on the provided configuration
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
config (CLFCBFConfig): Config object for the CLF-CBF. Contains info on the system dynamics, barrier
|
|
141
|
+
function, Lyapunov function, etc.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
CLFCBF: Control Lyapunov Function / Control Barrier Function instance
|
|
145
|
+
"""
|
|
146
|
+
instance = cls(
|
|
147
|
+
config.n,
|
|
148
|
+
config.m,
|
|
149
|
+
config.num_cbf,
|
|
150
|
+
config.num_clf,
|
|
151
|
+
config.u_min,
|
|
152
|
+
config.u_max,
|
|
153
|
+
config.control_constrained,
|
|
154
|
+
config.relax_cbf,
|
|
155
|
+
config.cbf_relaxation_penalty,
|
|
156
|
+
config.clf_relaxation_penalty,
|
|
157
|
+
config.h_1,
|
|
158
|
+
config.h_2,
|
|
159
|
+
config.f,
|
|
160
|
+
config.g,
|
|
161
|
+
config.alpha,
|
|
162
|
+
config.alpha_2,
|
|
163
|
+
config.V_1,
|
|
164
|
+
config.V_2,
|
|
165
|
+
config.gamma,
|
|
166
|
+
config.gamma_2,
|
|
167
|
+
config.H,
|
|
168
|
+
config.F,
|
|
169
|
+
config.solver_tol,
|
|
170
|
+
)
|
|
171
|
+
instance._validate_instance(*config.init_args)
|
|
172
|
+
return instance
|
|
173
|
+
|
|
174
|
+
def _validate_instance(self, *h_args) -> None:
|
|
175
|
+
"""Checks that the CLF-CBF is valid; warns the user if not
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
*h_args: Optional additional arguments for the barrier function.
|
|
179
|
+
"""
|
|
180
|
+
test_z = jnp.ones(self.n)
|
|
181
|
+
try:
|
|
182
|
+
test_lgh = self.Lgh(test_z, *h_args)
|
|
183
|
+
if jnp.allclose(test_lgh, 0):
|
|
184
|
+
print_warning(
|
|
185
|
+
"Lgh is zero. Consider increasing the relative degree or modifying the barrier function."
|
|
186
|
+
)
|
|
187
|
+
except TypeError:
|
|
188
|
+
print_warning(
|
|
189
|
+
"Cannot test Lgh; missing additional arguments.\n"
|
|
190
|
+
+ "Please provide an initial seed for these args in the config's init_args input"
|
|
191
|
+
)
|
|
192
|
+
test_lgv = self.LgV(test_z)
|
|
193
|
+
if jnp.allclose(test_lgv, 0):
|
|
194
|
+
print_warning(
|
|
195
|
+
"LgV is zero. Consider increasing the relative degree or modifying the Lyapunov function."
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
@conditional_jit(not DEBUG_CONTROLLER)
|
|
199
|
+
def controller(self, z: Array, z_des: Array, *h_args) -> Array:
|
|
200
|
+
"""Compute the CLF-CBF optimal control input, optimizing for the CLF objective while
|
|
201
|
+
satisfying the CBF safety constraint.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
z (Array): State, shape (n,)
|
|
205
|
+
z_des (Array): Desired state, shape (n,)
|
|
206
|
+
*h_args: Optional additional arguments for the barrier function.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
Array: Safe control input, shape (m,)
|
|
210
|
+
"""
|
|
211
|
+
P, q, A, b, G, h = self.qp_data(z, z_des, *h_args)
|
|
212
|
+
if self.relax_cbf:
|
|
213
|
+
x_qp, t_qp, s1_qp, s2_qp, z1_qp, z2_qp, converged, iters = self.qp_solver(
|
|
214
|
+
P,
|
|
215
|
+
q,
|
|
216
|
+
G,
|
|
217
|
+
h,
|
|
218
|
+
self.cbf_relaxation_penalty,
|
|
219
|
+
solver_tol=self.solver_tol,
|
|
220
|
+
)
|
|
221
|
+
else:
|
|
222
|
+
x_qp, s_qp, z_qp, y_qp, converged, iters = self.qp_solver(
|
|
223
|
+
P,
|
|
224
|
+
q,
|
|
225
|
+
A,
|
|
226
|
+
b,
|
|
227
|
+
G,
|
|
228
|
+
h,
|
|
229
|
+
solver_tol=self.solver_tol,
|
|
230
|
+
)
|
|
231
|
+
if DEBUG_CONTROLLER:
|
|
232
|
+
print(
|
|
233
|
+
f"{'Converged' if converged else 'Did not converge'}. Iterations: {iters}"
|
|
234
|
+
)
|
|
235
|
+
return x_qp[: self.m]
|
|
236
|
+
|
|
237
|
+
def h(self, z: ArrayLike, *h_args) -> Array:
|
|
238
|
+
"""Barrier function(s)
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
z (ArrayLike): State, shape (n,)
|
|
242
|
+
*h_args: Optional additional arguments for the barrier function.
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
Array: Barrier function evaluation, shape (num_barr,)
|
|
246
|
+
"""
|
|
247
|
+
|
|
248
|
+
# Take any relative-degree-2 barrier functions and convert them to relative-degree-1
|
|
249
|
+
def _h_2(state):
|
|
250
|
+
return self.h_2(state, *h_args)
|
|
251
|
+
|
|
252
|
+
h_2, dh_2_dt = jax.jvp(_h_2, (z,), (self.f(z),))
|
|
253
|
+
h_2_as_rd1 = dh_2_dt + self.alpha_2(h_2)
|
|
254
|
+
|
|
255
|
+
# Merge the relative-degree-1 and relative-degree-2 barrier functions
|
|
256
|
+
return jnp.concatenate([self.h_1(z, *h_args), h_2_as_rd1])
|
|
257
|
+
|
|
258
|
+
def h_and_Lfh( # pylint: disable=invalid-name
|
|
259
|
+
self, z: ArrayLike, *h_args
|
|
260
|
+
) -> Tuple[Array, Array]:
|
|
261
|
+
"""Lie derivative of the barrier function(s) wrt the autonomous dynamics `f(z)`
|
|
262
|
+
|
|
263
|
+
The evaluation of the barrier function is also returned "for free", a byproduct of the jacobian-vector-product
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
z (ArrayLike): State, shape (n,)
|
|
267
|
+
*h_args: Optional additional arguments for the barrier function.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
h (Array): Barrier function evaluation, shape (num_barr,)
|
|
271
|
+
Lfh (Array): Lie derivative of `h` w.r.t. `f`, shape (num_barr,)
|
|
272
|
+
"""
|
|
273
|
+
# Note: the below code is just a more efficient way of stating `Lfh = jax.jacobian(self.h)(z) @ self.f(z)`
|
|
274
|
+
# with the bonus benefit of also evaluating the barrier function
|
|
275
|
+
|
|
276
|
+
def _h(state):
|
|
277
|
+
return self.h(state, *h_args)
|
|
278
|
+
|
|
279
|
+
return jax.jvp(_h, (z,), (self.f(z),))
|
|
280
|
+
|
|
281
|
+
def Lgh(self, z: ArrayLike, *h_args) -> Array: # pylint: disable=invalid-name
|
|
282
|
+
"""Lie derivative of the barrier function(s) wrt the control dynamics `g(z)u`
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
z (ArrayLike): State, shape (n,)
|
|
286
|
+
*h_args: Optional additional arguments for the barrier function.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
Array: Lgh, shape (num_barr, m)
|
|
290
|
+
"""
|
|
291
|
+
# Note: the below code is just a more efficient way of stating `Lgh = jax.jacobian(self.h)(z) @ self.g(z)`
|
|
292
|
+
|
|
293
|
+
def _h(state):
|
|
294
|
+
return self.h(state, *h_args)
|
|
295
|
+
|
|
296
|
+
def _jvp(g_column):
|
|
297
|
+
return jax.jvp(_h, (z,), (g_column,))[1]
|
|
298
|
+
|
|
299
|
+
return jax.vmap(_jvp, in_axes=1, out_axes=1)(self.g(z))
|
|
300
|
+
|
|
301
|
+
## CLF functions ##
|
|
302
|
+
|
|
303
|
+
def V(self, z: ArrayLike) -> Array:
|
|
304
|
+
"""Control Lyapunov Function(s)
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
z (ArrayLike): State, shape (n,)
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
Array: CLF evaluation, shape (num_clf,)
|
|
311
|
+
"""
|
|
312
|
+
# Take any relative-degree-2 CLFs and convert them to relative-degree-1
|
|
313
|
+
# NOTE: If adding args to the CLF, create a wrapper func like with the barrier function
|
|
314
|
+
V_2, dV_2_dt = jax.jvp(self.V_2, (z,), (self.f(z),))
|
|
315
|
+
V2_rd1 = dV_2_dt + self.gamma_2(V_2)
|
|
316
|
+
|
|
317
|
+
# Merge the relative-degree-1 and relative-degree-2 CLFs
|
|
318
|
+
return jnp.concatenate([self.V_1(z), V2_rd1])
|
|
319
|
+
|
|
320
|
+
def V_and_LfV(self, z: ArrayLike) -> Tuple[Array, Array]:
|
|
321
|
+
"""Lie derivative of the CLF wrt the autonomous dynamics `f(z)`
|
|
322
|
+
|
|
323
|
+
The evaluation of the CLF is also returned "for free", a byproduct of the jacobian-vector-product
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
z (ArrayLike): State, shape (n,)
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
V (Array): CLF evaluation, shape (1,)
|
|
330
|
+
LfV (Array): Lie derivative of `V` w.r.t. `f`, shape (1,)
|
|
331
|
+
"""
|
|
332
|
+
return jax.jvp(self.V, (z,), (self.f(z),))
|
|
333
|
+
|
|
334
|
+
def LgV(self, z: ArrayLike) -> Array:
|
|
335
|
+
"""Lie derivative of the CLF wrt the control dynamics `g(z)u`
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
z (ArrayLike): State, shape (n,)
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
Array: LgV, shape (m,)
|
|
342
|
+
"""
|
|
343
|
+
|
|
344
|
+
def _jvp(g_column):
|
|
345
|
+
return jax.jvp(self.V, (z,), (g_column,))[1]
|
|
346
|
+
|
|
347
|
+
return jax.vmap(_jvp, in_axes=1, out_axes=1)(self.g(z))
|
|
348
|
+
|
|
349
|
+
## QP Matrices ##
|
|
350
|
+
|
|
351
|
+
def P_qp( # pylint: disable=invalid-name
|
|
352
|
+
self, z: Array, z_des: Array, *h_args
|
|
353
|
+
) -> Array:
|
|
354
|
+
"""Quadratic term in the QP objective (`minimize 0.5 * x^T P x + q^T x`)
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
z (Array): State, shape (n,)
|
|
358
|
+
z_des (Array): Desired state, shape (n,)
|
|
359
|
+
*h_args: Optional additional arguments for the barrier function.
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
Array: P matrix, shape (m, m)
|
|
363
|
+
"""
|
|
364
|
+
return jnp.block(
|
|
365
|
+
[
|
|
366
|
+
[self.H(z), jnp.zeros((self.m, 1))],
|
|
367
|
+
[jnp.zeros((1, self.m)), jnp.atleast_1d(self.clf_relaxation_penalty)],
|
|
368
|
+
]
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
def q_qp(self, z: Array, z_des: Array, *h_args) -> Array:
|
|
372
|
+
"""Linear term in the QP objective (`minimize 0.5 * x^T P x + q^T x`)
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
z (Array): State, shape (n,)
|
|
376
|
+
z_des (Array): Desired state, shape (n,)
|
|
377
|
+
*h_args: Optional additional arguments for the barrier function.
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
Array: Q vector, shape (m,)
|
|
381
|
+
"""
|
|
382
|
+
return jnp.concatenate([self.F(z), jnp.array([0.0])])
|
|
383
|
+
|
|
384
|
+
def G_qp( # pylint: disable=invalid-name
|
|
385
|
+
self, z: Array, z_des: Array, *h_args
|
|
386
|
+
) -> Array:
|
|
387
|
+
"""Inequality constraint matrix for the QP (`Gx <= h`)
|
|
388
|
+
|
|
389
|
+
Note:
|
|
390
|
+
The number of constraints depends on if we have control constraints or not.
|
|
391
|
+
Without control constraints, `num_constraints == num_barriers`.
|
|
392
|
+
With control constraints, `num_constraints == num_barriers + 2*m`
|
|
393
|
+
|
|
394
|
+
Args:
|
|
395
|
+
z (Array): State, shape (n,)
|
|
396
|
+
z_des (Array): Desired state, shape (n,)
|
|
397
|
+
*h_args: Optional additional arguments for the barrier function.
|
|
398
|
+
|
|
399
|
+
Returns:
|
|
400
|
+
Array: G matrix, shape (num_constraints, m)
|
|
401
|
+
"""
|
|
402
|
+
G = jnp.block(
|
|
403
|
+
[
|
|
404
|
+
[self.LgV(z), -1.0 * jnp.ones((self.num_clf, 1))],
|
|
405
|
+
[-self.Lgh(z, *h_args), jnp.zeros((self.num_cbf, 1))],
|
|
406
|
+
]
|
|
407
|
+
)
|
|
408
|
+
if self.control_constrained:
|
|
409
|
+
return jnp.block(
|
|
410
|
+
[
|
|
411
|
+
[G],
|
|
412
|
+
[jnp.eye(self.m), jnp.zeros((self.m, 1))],
|
|
413
|
+
[-jnp.eye(self.m), jnp.zeros((self.m, 1))],
|
|
414
|
+
]
|
|
415
|
+
)
|
|
416
|
+
else:
|
|
417
|
+
return G
|
|
418
|
+
|
|
419
|
+
def h_qp(self, z: Array, z_des: Array, *h_args) -> Array:
|
|
420
|
+
"""Upper bound on constraints for the QP (`Gx <= h`)
|
|
421
|
+
|
|
422
|
+
Note:
|
|
423
|
+
The number of constraints depends on if we have control constraints or not.
|
|
424
|
+
Without control constraints, `num_constraints == num_barriers`.
|
|
425
|
+
With control constraints, `num_constraints == num_barriers + 2*m`
|
|
426
|
+
|
|
427
|
+
Args:
|
|
428
|
+
z (Array): State, shape (n,)
|
|
429
|
+
z_des (Array): Desired state, shape (n,)
|
|
430
|
+
*h_args: Optional additional arguments for the barrier function.
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
Array: h vector, shape (num_constraints,)
|
|
434
|
+
"""
|
|
435
|
+
hz, lfh = self.h_and_Lfh(z, *h_args)
|
|
436
|
+
vz, lfv = self.V_and_LfV(z)
|
|
437
|
+
h = jnp.concatenate(
|
|
438
|
+
[
|
|
439
|
+
-lfv - self.gamma(vz),
|
|
440
|
+
self.alpha(hz) + lfh,
|
|
441
|
+
]
|
|
442
|
+
)
|
|
443
|
+
if self.control_constrained:
|
|
444
|
+
return jnp.concatenate(
|
|
445
|
+
[h, jnp.asarray(self.u_max), -jnp.asarray(self.u_min)]
|
|
446
|
+
)
|
|
447
|
+
else:
|
|
448
|
+
return h
|
|
449
|
+
|
|
450
|
+
@conditional_jit(not DEBUG_QP_DATA)
|
|
451
|
+
def qp_data(
|
|
452
|
+
self, z: Array, z_des: Array, *h_args
|
|
453
|
+
) -> Tuple[Array, Array, Array, Array, Array, Array]:
|
|
454
|
+
"""Constructs the QP matrices based on the current state and desired control
|
|
455
|
+
|
|
456
|
+
i.e. the matrices/vectors (P, q, A, b, G, h) for the optimization problem:
|
|
457
|
+
|
|
458
|
+
```
|
|
459
|
+
minimize 0.5 * x^T P x + q^T x
|
|
460
|
+
subject to A x == b
|
|
461
|
+
G x <= h
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
Note:
|
|
465
|
+
- CBFs do not rely on equality constraints, so `A` and `b` are empty.
|
|
466
|
+
- The number of constraints depends on if we have control constraints or not.
|
|
467
|
+
Without control constraints, `num_constraints == num_barriers`.
|
|
468
|
+
With control constraints, `num_constraints == num_barriers + 2*m`
|
|
469
|
+
|
|
470
|
+
Args:
|
|
471
|
+
z (Array): State, shape (n,)
|
|
472
|
+
z_des (Array): Desired state, shape (n,)
|
|
473
|
+
*h_args: Optional additional arguments for the barrier function.
|
|
474
|
+
|
|
475
|
+
Returns:
|
|
476
|
+
P (Array): Quadratic term in the QP objective, shape (m + 1, m + 1)
|
|
477
|
+
q (Array): Linear term in the QP objective, shape (m + 1,)
|
|
478
|
+
A (Array): Equality constraint matrix, shape (0, m + 1)
|
|
479
|
+
b (Array): Equality constraint vector, shape (0,)
|
|
480
|
+
G (Array): Inequality constraint matrix, shape (num_constraints, m + 1)
|
|
481
|
+
h (Array): Upper bound on constraints, shape (num_constraints,)
|
|
482
|
+
"""
|
|
483
|
+
return (
|
|
484
|
+
self.P_qp(z, z_des, *h_args),
|
|
485
|
+
self.q_qp(z, z_des, *h_args),
|
|
486
|
+
jnp.zeros((0, self.m + 1)), # Equality matrix (not used for CLF-CBF)
|
|
487
|
+
jnp.zeros(0), # Equality vector (not used for CLF-CBF)
|
|
488
|
+
self.G_qp(z, z_des, *h_args),
|
|
489
|
+
self.h_qp(z, z_des, *h_args),
|
|
490
|
+
)
|
cbfpy/config/__init__.py
ADDED
|
File without changes
|