cbfpy 0.0.1__py3-none-any.whl → 0.0.4__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/cbfs/cbf.py +46 -70
- cbfpy/cbfs/clf_cbf.py +81 -86
- cbfpy/config/cbf_config.py +106 -56
- cbfpy/config/clf_cbf_config.py +79 -29
- cbfpy/envs/base_env.py +3 -3
- cbfpy/envs/drone_env.py +1 -1
- cbfpy/examples/adaptive_cruise_control_demo.py +5 -4
- cbfpy/examples/drone_demo.py +8 -6
- cbfpy/examples/joint_limits_demo.py +1 -1
- cbfpy/examples/point_robot_obstacle_demo.py +7 -7
- {cbfpy-0.0.1.dist-info → cbfpy-0.0.4.dist-info}/METADATA +24 -17
- cbfpy-0.0.4.dist-info/RECORD +32 -0
- {cbfpy-0.0.1.dist-info → cbfpy-0.0.4.dist-info}/WHEEL +1 -1
- test/test_speed.py +2 -2
- cbfpy/temp/test_import.py +0 -3
- cbfpy-0.0.1.dist-info/RECORD +0 -33
- {cbfpy-0.0.1.dist-info → cbfpy-0.0.4.dist-info/licenses}/LICENSE +0 -0
- {cbfpy-0.0.1.dist-info → cbfpy-0.0.4.dist-info}/top_level.txt +0 -0
cbfpy/cbfs/cbf.py
CHANGED
|
@@ -30,17 +30,12 @@ import jax
|
|
|
30
30
|
import jax.numpy as jnp
|
|
31
31
|
from jax import Array
|
|
32
32
|
from jax.typing import ArrayLike
|
|
33
|
+
import numpy as np
|
|
33
34
|
import qpax
|
|
34
35
|
|
|
35
36
|
from cbfpy.config.cbf_config import CBFConfig
|
|
36
|
-
from cbfpy.utils.jax_utils import conditional_jit
|
|
37
37
|
from cbfpy.utils.general_utils import print_warning
|
|
38
38
|
|
|
39
|
-
# Debugging flags to disable jit in specific sections of the code.
|
|
40
|
-
# Note: If any higher-level jits exist, those must also be set to debug (disable jit)
|
|
41
|
-
DEBUG_SAFETY_FILTER = False
|
|
42
|
-
DEBUG_QP_DATA = False
|
|
43
|
-
|
|
44
39
|
|
|
45
40
|
@jax.tree_util.register_static
|
|
46
41
|
class CBF:
|
|
@@ -73,8 +68,8 @@ class CBF:
|
|
|
73
68
|
u_min: Optional[tuple],
|
|
74
69
|
u_max: Optional[tuple],
|
|
75
70
|
control_constrained: bool,
|
|
76
|
-
|
|
77
|
-
|
|
71
|
+
relax_qp: bool,
|
|
72
|
+
constraint_relaxation_penalties: tuple,
|
|
78
73
|
h_1: Callable[[ArrayLike], Array],
|
|
79
74
|
h_2: Callable[[ArrayLike], Array],
|
|
80
75
|
f: Callable[[ArrayLike], Array],
|
|
@@ -91,8 +86,8 @@ class CBF:
|
|
|
91
86
|
self.u_min = u_min
|
|
92
87
|
self.u_max = u_max
|
|
93
88
|
self.control_constrained = control_constrained
|
|
94
|
-
self.
|
|
95
|
-
self.
|
|
89
|
+
self.relax_qp = relax_qp
|
|
90
|
+
self.constraint_relaxation_penalties = constraint_relaxation_penalties
|
|
96
91
|
self.h_1 = h_1
|
|
97
92
|
self.h_2 = h_2
|
|
98
93
|
self.f = f
|
|
@@ -102,10 +97,6 @@ class CBF:
|
|
|
102
97
|
self.P_config = P
|
|
103
98
|
self.q_config = q
|
|
104
99
|
self.solver_tol = solver_tol
|
|
105
|
-
if relax_cbf:
|
|
106
|
-
self.qp_solver: Callable = jax.jit(qpax.solve_qp_elastic)
|
|
107
|
-
else:
|
|
108
|
-
self.qp_solver: Callable = jax.jit(qpax.solve_qp)
|
|
109
100
|
|
|
110
101
|
@classmethod
|
|
111
102
|
def from_config(cls, config: CBFConfig) -> "CBF":
|
|
@@ -124,8 +115,8 @@ class CBF:
|
|
|
124
115
|
config.u_min,
|
|
125
116
|
config.u_max,
|
|
126
117
|
config.control_constrained,
|
|
127
|
-
config.
|
|
128
|
-
config.
|
|
118
|
+
config.relax_qp,
|
|
119
|
+
config.constraint_relaxation_penalties,
|
|
129
120
|
config.h_1,
|
|
130
121
|
config.h_2,
|
|
131
122
|
config.f,
|
|
@@ -136,19 +127,16 @@ class CBF:
|
|
|
136
127
|
config.q,
|
|
137
128
|
config.solver_tol,
|
|
138
129
|
)
|
|
139
|
-
instance._validate_instance(*config.init_args)
|
|
130
|
+
instance._validate_instance(*config.init_args, **config.init_kwargs)
|
|
140
131
|
return instance
|
|
141
132
|
|
|
142
|
-
def _validate_instance(self, *
|
|
143
|
-
"""Checks that the CBF is valid; warns the user if not
|
|
133
|
+
def _validate_instance(self, *args, **kwargs) -> None:
|
|
134
|
+
"""Checks that the CBF is valid; warns the user if not"""
|
|
144
135
|
|
|
145
|
-
Args:
|
|
146
|
-
*h_args: Optional additional arguments for the barrier function.
|
|
147
|
-
"""
|
|
148
136
|
try:
|
|
149
137
|
# TODO: Decide if this should be checked on a row-by-row basis or via the full matrix
|
|
150
|
-
test_lgh = self.Lgh(
|
|
151
|
-
if
|
|
138
|
+
test_lgh = self.Lgh(np.ones(self.n), *args, **kwargs)
|
|
139
|
+
if np.allclose(test_lgh, 0):
|
|
152
140
|
print_warning(
|
|
153
141
|
"Lgh is zero. Consider increasing the relative degree or modifying the barrier function."
|
|
154
142
|
)
|
|
@@ -158,30 +146,29 @@ class CBF:
|
|
|
158
146
|
+ "Please provide an initial seed for these args in the config's init_args input"
|
|
159
147
|
)
|
|
160
148
|
|
|
161
|
-
@
|
|
162
|
-
def safety_filter(self, z: Array, u_des: Array, *
|
|
149
|
+
@jax.jit
|
|
150
|
+
def safety_filter(self, z: Array, u_des: Array, *args, **kwargs) -> Array:
|
|
163
151
|
"""Apply the CBF safety filter to a nominal control
|
|
164
152
|
|
|
165
153
|
Args:
|
|
166
154
|
z (Array): State, shape (n,)
|
|
167
155
|
u_des (Array): Desired control input, shape (m,)
|
|
168
|
-
*h_args: Optional additional arguments for the barrier function.
|
|
169
156
|
|
|
170
157
|
Returns:
|
|
171
158
|
Array: Safe control input, shape (m,)
|
|
172
159
|
"""
|
|
173
|
-
P, q, A, b, G, h = self.qp_data(z, u_des, *
|
|
174
|
-
if self.
|
|
175
|
-
x_qp
|
|
160
|
+
P, q, A, b, G, h = self.qp_data(z, u_des, *args, **kwargs)
|
|
161
|
+
if self.relax_qp:
|
|
162
|
+
x_qp = qpax.solve_qp_elastic_primal(
|
|
176
163
|
P,
|
|
177
164
|
q,
|
|
178
165
|
G,
|
|
179
166
|
h,
|
|
180
|
-
self.
|
|
167
|
+
penalty=jnp.asarray(self.constraint_relaxation_penalties),
|
|
181
168
|
solver_tol=self.solver_tol,
|
|
182
169
|
)
|
|
183
170
|
else:
|
|
184
|
-
x_qp, s_qp, z_qp, y_qp, converged, iters =
|
|
171
|
+
x_qp, s_qp, z_qp, y_qp, converged, iters = qpax.solve_qp(
|
|
185
172
|
P,
|
|
186
173
|
q,
|
|
187
174
|
A,
|
|
@@ -190,18 +177,13 @@ class CBF:
|
|
|
190
177
|
h,
|
|
191
178
|
solver_tol=self.solver_tol,
|
|
192
179
|
)
|
|
193
|
-
if DEBUG_SAFETY_FILTER:
|
|
194
|
-
print(
|
|
195
|
-
f"{'Converged' if converged else 'Did not converge'}. Iterations: {iters}"
|
|
196
|
-
)
|
|
197
180
|
return x_qp[: self.m]
|
|
198
181
|
|
|
199
|
-
def h(self, z: ArrayLike, *
|
|
182
|
+
def h(self, z: ArrayLike, *args, **kwargs) -> Array:
|
|
200
183
|
"""Barrier function(s)
|
|
201
184
|
|
|
202
185
|
Args:
|
|
203
186
|
z (ArrayLike): State, shape (n,)
|
|
204
|
-
*h_args: Optional additional arguments for the barrier function.
|
|
205
187
|
|
|
206
188
|
Returns:
|
|
207
189
|
Array: Barrier function evaluation, shape (num_barr,)
|
|
@@ -209,16 +191,16 @@ class CBF:
|
|
|
209
191
|
|
|
210
192
|
# Take any relative-degree-2 barrier functions and convert them to relative-degree-1
|
|
211
193
|
def _h_2(state):
|
|
212
|
-
return self.h_2(state, *
|
|
194
|
+
return self.h_2(state, *args, **kwargs)
|
|
213
195
|
|
|
214
|
-
h_2, dh_2_dt = jax.jvp(_h_2, (z,), (self.f(z),))
|
|
215
|
-
h_2_as_rd1 = dh_2_dt + self.alpha_2(h_2)
|
|
196
|
+
h_2, dh_2_dt = jax.jvp(_h_2, (z,), (self.f(z, *args, **kwargs),))
|
|
197
|
+
h_2_as_rd1 = dh_2_dt + self.alpha_2(h_2, *args, **kwargs)
|
|
216
198
|
|
|
217
199
|
# Merge the relative-degree-1 and relative-degree-2 barrier functions
|
|
218
|
-
return jnp.concatenate([self.h_1(z, *
|
|
200
|
+
return jnp.concatenate([self.h_1(z, *args, **kwargs), h_2_as_rd1])
|
|
219
201
|
|
|
220
202
|
def h_and_Lfh( # pylint: disable=invalid-name
|
|
221
|
-
self, z: ArrayLike, *
|
|
203
|
+
self, z: ArrayLike, *args, **kwargs
|
|
222
204
|
) -> Tuple[Array, Array]:
|
|
223
205
|
"""Lie derivative of the barrier function(s) wrt the autonomous dynamics `f(z)`
|
|
224
206
|
|
|
@@ -226,7 +208,6 @@ class CBF:
|
|
|
226
208
|
|
|
227
209
|
Args:
|
|
228
210
|
z (ArrayLike): State, shape (n,)
|
|
229
|
-
*h_args: Optional additional arguments for the barrier function.
|
|
230
211
|
|
|
231
212
|
Returns:
|
|
232
213
|
h (Array): Barrier function evaluation, shape (num_barr,)
|
|
@@ -236,16 +217,17 @@ class CBF:
|
|
|
236
217
|
# with the bonus benefit of also evaluating the barrier function
|
|
237
218
|
|
|
238
219
|
def _h(state):
|
|
239
|
-
return self.h(state, *
|
|
220
|
+
return self.h(state, *args, **kwargs)
|
|
240
221
|
|
|
241
|
-
return jax.jvp(_h, (z,), (self.f(z),))
|
|
222
|
+
return jax.jvp(_h, (z,), (self.f(z, *args, **kwargs),))
|
|
242
223
|
|
|
243
|
-
def Lgh(
|
|
224
|
+
def Lgh(
|
|
225
|
+
self, z: ArrayLike, *args, **kwargs
|
|
226
|
+
) -> Array: # pylint: disable=invalid-name
|
|
244
227
|
"""Lie derivative of the barrier function(s) wrt the control dynamics `g(z)u`
|
|
245
228
|
|
|
246
229
|
Args:
|
|
247
230
|
z (ArrayLike): State, shape (n,)
|
|
248
|
-
*h_args: Optional additional arguments for the barrier function.
|
|
249
231
|
|
|
250
232
|
Returns:
|
|
251
233
|
Array: Lgh, shape (num_barr, m)
|
|
@@ -253,47 +235,45 @@ class CBF:
|
|
|
253
235
|
# Note: the below code is just a more efficient way of stating `Lgh = jax.jacobian(self.h)(z) @ self.g(z)`
|
|
254
236
|
|
|
255
237
|
def _h(state):
|
|
256
|
-
return self.h(state, *
|
|
238
|
+
return self.h(state, *args, **kwargs)
|
|
257
239
|
|
|
258
240
|
def _jvp(g_column):
|
|
259
241
|
return jax.jvp(_h, (z,), (g_column,))[1]
|
|
260
242
|
|
|
261
|
-
return jax.vmap(_jvp, in_axes=1, out_axes=1)(self.g(z))
|
|
243
|
+
return jax.vmap(_jvp, in_axes=1, out_axes=1)(self.g(z, *args, **kwargs))
|
|
262
244
|
|
|
263
245
|
## QP Matrices ##
|
|
264
246
|
|
|
265
247
|
def P_qp( # pylint: disable=invalid-name
|
|
266
|
-
self, z: Array, u_des: Array, *
|
|
248
|
+
self, z: Array, u_des: Array, *args, **kwargs
|
|
267
249
|
) -> Array:
|
|
268
250
|
"""Quadratic term in the QP objective (`minimize 0.5 * x^T P x + q^T x`)
|
|
269
251
|
|
|
270
252
|
Args:
|
|
271
253
|
z (Array): State, shape (n,)
|
|
272
254
|
u_des (Array): Desired control input, shape (m,)
|
|
273
|
-
*h_args: Optional additional arguments for the barrier function.
|
|
274
255
|
|
|
275
256
|
Returns:
|
|
276
257
|
Array: P matrix, shape (m, m)
|
|
277
258
|
"""
|
|
278
259
|
# This is user-modifiable in the config, but defaults to 2 * I for the standard min-norm CBF objective
|
|
279
|
-
return self.P_config(z, u_des, *
|
|
260
|
+
return self.P_config(z, u_des, *args, **kwargs)
|
|
280
261
|
|
|
281
|
-
def q_qp(self, z: Array, u_des: Array, *
|
|
262
|
+
def q_qp(self, z: Array, u_des: Array, *args, **kwargs) -> Array:
|
|
282
263
|
"""Linear term in the QP objective (`minimize 0.5 * x^T P x + q^T x`)
|
|
283
264
|
|
|
284
265
|
Args:
|
|
285
266
|
z (Array): State, shape (n,)
|
|
286
267
|
u_des (Array): Desired control input, shape (m,)
|
|
287
|
-
*h_args: Optional additional arguments for the barrier function.
|
|
288
268
|
|
|
289
269
|
Returns:
|
|
290
270
|
Array: q vector, shape (m,)
|
|
291
271
|
"""
|
|
292
272
|
# This is user-modifiable in the config, but defaults to -2 * u_des for the standard min-norm CBF objective
|
|
293
|
-
return self.q_config(z, u_des, *
|
|
273
|
+
return self.q_config(z, u_des, *args, **kwargs)
|
|
294
274
|
|
|
295
275
|
def G_qp( # pylint: disable=invalid-name
|
|
296
|
-
self, z: Array, u_des: Array, *
|
|
276
|
+
self, z: Array, u_des: Array, *args, **kwargs
|
|
297
277
|
) -> Array:
|
|
298
278
|
"""Inequality constraint matrix for the QP (`Gx <= h`)
|
|
299
279
|
|
|
@@ -305,18 +285,17 @@ class CBF:
|
|
|
305
285
|
Args:
|
|
306
286
|
z (Array): State, shape (n,)
|
|
307
287
|
u_des (Array): Desired control input, shape (m,)
|
|
308
|
-
*h_args: Optional additional arguments for the barrier function.
|
|
309
288
|
|
|
310
289
|
Returns:
|
|
311
290
|
Array: G matrix, shape (num_constraints, m)
|
|
312
291
|
"""
|
|
313
|
-
G = -self.Lgh(z, *
|
|
292
|
+
G = -self.Lgh(z, *args, **kwargs)
|
|
314
293
|
if self.control_constrained:
|
|
315
294
|
return jnp.block([[G], [jnp.eye(self.m)], [-jnp.eye(self.m)]])
|
|
316
295
|
else:
|
|
317
296
|
return G
|
|
318
297
|
|
|
319
|
-
def h_qp(self, z: Array, u_des: Array, *
|
|
298
|
+
def h_qp(self, z: Array, u_des: Array, *args, **kwargs) -> Array:
|
|
320
299
|
"""Upper bound on constraints for the QP (`Gx <= h`)
|
|
321
300
|
|
|
322
301
|
Note:
|
|
@@ -327,13 +306,12 @@ class CBF:
|
|
|
327
306
|
Args:
|
|
328
307
|
z (Array): State, shape (n,)
|
|
329
308
|
u_des (Array): Desired control input, shape (m,)
|
|
330
|
-
*h_args: Optional additional arguments for the barrier function.
|
|
331
309
|
|
|
332
310
|
Returns:
|
|
333
311
|
Array: h vector, shape (num_constraints,)
|
|
334
312
|
"""
|
|
335
|
-
hz, lfh = self.h_and_Lfh(z, *
|
|
336
|
-
h = self.alpha(hz) + lfh
|
|
313
|
+
hz, lfh = self.h_and_Lfh(z, *args, **kwargs)
|
|
314
|
+
h = self.alpha(hz, *args, **kwargs) + lfh
|
|
337
315
|
if self.control_constrained:
|
|
338
316
|
return jnp.concatenate(
|
|
339
317
|
[h, jnp.asarray(self.u_max), -jnp.asarray(self.u_min)]
|
|
@@ -341,9 +319,8 @@ class CBF:
|
|
|
341
319
|
else:
|
|
342
320
|
return h
|
|
343
321
|
|
|
344
|
-
@conditional_jit(not DEBUG_QP_DATA)
|
|
345
322
|
def qp_data(
|
|
346
|
-
self, z: Array, u_des: Array, *
|
|
323
|
+
self, z: Array, u_des: Array, *args, **kwargs
|
|
347
324
|
) -> Tuple[Array, Array, Array, Array, Array, Array]:
|
|
348
325
|
"""Constructs the QP matrices based on the current state and desired control
|
|
349
326
|
|
|
@@ -364,7 +341,6 @@ class CBF:
|
|
|
364
341
|
Args:
|
|
365
342
|
z (Array): State, shape (n,)
|
|
366
343
|
u_des (Array): Desired control input, shape (m,)
|
|
367
|
-
*h_args: Optional additional arguments for the barrier function.
|
|
368
344
|
|
|
369
345
|
Returns:
|
|
370
346
|
P (Array): Quadratic term in the QP objective, shape (m, m)
|
|
@@ -375,10 +351,10 @@ class CBF:
|
|
|
375
351
|
h (Array): Upper bound on constraints, shape (num_constraints,)
|
|
376
352
|
"""
|
|
377
353
|
return (
|
|
378
|
-
self.P_qp(z, u_des, *
|
|
379
|
-
self.q_qp(z, u_des, *
|
|
354
|
+
self.P_qp(z, u_des, *args, **kwargs),
|
|
355
|
+
self.q_qp(z, u_des, *args, **kwargs),
|
|
380
356
|
jnp.zeros((0, self.m)), # Equality matrix (not used for CBF)
|
|
381
357
|
jnp.zeros(0), # Equality vector (not used for CBF)
|
|
382
|
-
self.G_qp(z, u_des, *
|
|
383
|
-
self.h_qp(z, u_des, *
|
|
358
|
+
self.G_qp(z, u_des, *args, **kwargs),
|
|
359
|
+
self.h_qp(z, u_des, *args, **kwargs),
|
|
384
360
|
)
|