cbfpy 0.0.1__tar.gz → 0.0.3__tar.gz
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-0.0.1 → cbfpy-0.0.3}/PKG-INFO +23 -17
- {cbfpy-0.0.1 → cbfpy-0.0.3}/README.md +14 -10
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/cbfs/cbf.py +11 -26
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/cbfs/clf_cbf.py +36 -37
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/config/cbf_config.py +56 -11
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/config/clf_cbf_config.py +57 -14
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/envs/base_env.py +3 -3
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/envs/drone_env.py +1 -1
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/examples/adaptive_cruise_control_demo.py +5 -4
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/examples/drone_demo.py +4 -2
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/examples/joint_limits_demo.py +1 -1
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/examples/point_robot_obstacle_demo.py +2 -2
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy.egg-info/PKG-INFO +23 -17
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy.egg-info/SOURCES.txt +0 -1
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy.egg-info/requires.txt +7 -5
- {cbfpy-0.0.1 → cbfpy-0.0.3}/pyproject.toml +11 -9
- {cbfpy-0.0.1 → cbfpy-0.0.3}/test/test_speed.py +2 -2
- cbfpy-0.0.1/cbfpy/temp/test_import.py +0 -3
- {cbfpy-0.0.1 → cbfpy-0.0.3}/LICENSE +0 -0
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/__init__.py +0 -0
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/cbfs/__init__.py +0 -0
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/config/__init__.py +0 -0
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/envs/__init__.py +0 -0
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/envs/arm_envs.py +0 -0
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/envs/car_env.py +0 -0
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/envs/point_robot_envs.py +0 -0
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/examples/__init__.py +0 -0
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/examples/point_robot_demo.py +0 -0
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/utils/__init__.py +0 -0
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/utils/general_utils.py +0 -0
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/utils/jax_utils.py +0 -0
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/utils/math_utils.py +0 -0
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/utils/visualization.py +0 -0
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy.egg-info/dependency_links.txt +0 -0
- {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy.egg-info/top_level.txt +0 -0
- {cbfpy-0.0.1 → cbfpy-0.0.3}/setup.cfg +0 -0
- {cbfpy-0.0.1 → cbfpy-0.0.3}/test/__init__.py +0 -0
- {cbfpy-0.0.1 → cbfpy-0.0.3}/test/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: cbfpy
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: Control Barrier Functions in Python
|
|
5
5
|
Author-email: Daniel Morton <danielpmorton@gmail.com>
|
|
6
6
|
Project-URL: Documentation, https://danielpmorton.github.io/cbfpy/
|
|
@@ -8,15 +8,14 @@ Project-URL: Repository, https://github.com/danielpmorton/cbfpy/
|
|
|
8
8
|
Keywords: control,barrier,function,CBF,Jax
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
10
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
-
Classifier: Operating System :: OS Independent
|
|
12
11
|
Description-Content-Type: text/markdown
|
|
13
12
|
License-File: LICENSE
|
|
14
|
-
Requires-Dist: numpy
|
|
13
|
+
Requires-Dist: numpy
|
|
15
14
|
Requires-Dist: jax
|
|
16
15
|
Requires-Dist: jaxlib
|
|
17
16
|
Requires-Dist: qpax
|
|
18
17
|
Provides-Extra: examples
|
|
19
|
-
Requires-Dist: pybullet; extra == "examples"
|
|
18
|
+
Requires-Dist: pybullet>=3.2.7; extra == "examples"
|
|
20
19
|
Requires-Dist: pygame; extra == "examples"
|
|
21
20
|
Requires-Dist: wheel; extra == "examples"
|
|
22
21
|
Requires-Dist: matplotlib; extra == "examples"
|
|
@@ -26,12 +25,19 @@ Requires-Dist: mkdocstrings[python]; extra == "dev"
|
|
|
26
25
|
Requires-Dist: pylint; extra == "dev"
|
|
27
26
|
Requires-Dist: black; extra == "dev"
|
|
28
27
|
Provides-Extra: all
|
|
29
|
-
Requires-Dist:
|
|
30
|
-
Requires-Dist: black; extra == "all"
|
|
31
|
-
Requires-Dist: pybullet; extra == "all"
|
|
28
|
+
Requires-Dist: pybullet>=3.2.7; extra == "all"
|
|
32
29
|
Requires-Dist: pygame; extra == "all"
|
|
30
|
+
Requires-Dist: wheel; extra == "all"
|
|
31
|
+
Requires-Dist: matplotlib; extra == "all"
|
|
33
32
|
Requires-Dist: mkdocs-material; extra == "all"
|
|
34
33
|
Requires-Dist: mkdocstrings[python]; extra == "all"
|
|
34
|
+
Requires-Dist: pylint; extra == "all"
|
|
35
|
+
Requires-Dist: black; extra == "all"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
<div align="center">
|
|
39
|
+
<img src="https://github.com/user-attachments/assets/0304752c-cb75-4d53-b45f-b6b1a0912d9c" alt="logo"></img>
|
|
40
|
+
</div>
|
|
35
41
|
|
|
36
42
|
# CBFpy: Control Barrier Functions in Python and Jax
|
|
37
43
|
|
|
@@ -43,20 +49,20 @@ CBFpy is an easy-to-use and high-performance framework for constructing and solv
|
|
|
43
49
|
|
|
44
50
|
For API reference, see the following [documentation](https://danielpmorton.github.io/cbfpy)
|
|
45
51
|
|
|
46
|
-
If you use CBFpy in your research, please
|
|
52
|
+
If you use CBFpy in your research, please cite the following [paper](https://arxiv.org/abs/2503.06736):
|
|
47
53
|
|
|
48
54
|
```
|
|
49
|
-
@
|
|
50
|
-
author = {Morton, Daniel},
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
month = Dec,
|
|
56
|
-
year = {2024}
|
|
55
|
+
@article{morton2025oscbf,
|
|
56
|
+
author = {Morton, Daniel and Pavone, Marco},
|
|
57
|
+
title = {Safe, Task-Consistent Manipulation with Operational Space Control Barrier Functions},
|
|
58
|
+
year = {2025},
|
|
59
|
+
journal = {arXiv preprint arXiv:2503.06736},
|
|
60
|
+
note = {Accepted to IEEE/RSJ International Conference on Intelligent Robots and Systems (IROS), Hangzhou, 2025},
|
|
57
61
|
}
|
|
58
62
|
```
|
|
59
63
|
|
|
64
|
+
[](https://arxiv.org/abs/2503.06736)
|
|
65
|
+
|
|
60
66
|
## Installation
|
|
61
67
|
|
|
62
68
|
### From PyPI
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="https://github.com/user-attachments/assets/0304752c-cb75-4d53-b45f-b6b1a0912d9c" alt="logo"></img>
|
|
3
|
+
</div>
|
|
4
|
+
|
|
1
5
|
# CBFpy: Control Barrier Functions in Python and Jax
|
|
2
6
|
|
|
3
7
|
CBFpy is an easy-to-use and high-performance framework for constructing and solving Control Barrier Functions (CBFs) and Control Lyapunov Functions (CLFs), using [Jax](https://github.com/google/jax) for:
|
|
@@ -8,20 +12,20 @@ CBFpy is an easy-to-use and high-performance framework for constructing and solv
|
|
|
8
12
|
|
|
9
13
|
For API reference, see the following [documentation](https://danielpmorton.github.io/cbfpy)
|
|
10
14
|
|
|
11
|
-
If you use CBFpy in your research, please
|
|
15
|
+
If you use CBFpy in your research, please cite the following [paper](https://arxiv.org/abs/2503.06736):
|
|
12
16
|
|
|
13
17
|
```
|
|
14
|
-
@
|
|
15
|
-
author = {Morton, Daniel},
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
month = Dec,
|
|
21
|
-
year = {2024}
|
|
18
|
+
@article{morton2025oscbf,
|
|
19
|
+
author = {Morton, Daniel and Pavone, Marco},
|
|
20
|
+
title = {Safe, Task-Consistent Manipulation with Operational Space Control Barrier Functions},
|
|
21
|
+
year = {2025},
|
|
22
|
+
journal = {arXiv preprint arXiv:2503.06736},
|
|
23
|
+
note = {Accepted to IEEE/RSJ International Conference on Intelligent Robots and Systems (IROS), Hangzhou, 2025},
|
|
22
24
|
}
|
|
23
25
|
```
|
|
24
26
|
|
|
27
|
+
[](https://arxiv.org/abs/2503.06736)
|
|
28
|
+
|
|
25
29
|
## Installation
|
|
26
30
|
|
|
27
31
|
### From PyPI
|
|
@@ -188,4 +192,4 @@ Use a CBF to keep a drone inside a safe box, while avoiding a moving obstacle. T
|
|
|
188
192
|
|
|
189
193
|
This is the same CBF which was used in the ["Drone Fencing" demo](https://danielpmorton.github.io/drone_fencing/) at the Stanford Robotics center.
|
|
190
194
|
|
|
191
|
-

|
|
195
|
+

|
|
@@ -33,14 +33,8 @@ from jax.typing import ArrayLike
|
|
|
33
33
|
import qpax
|
|
34
34
|
|
|
35
35
|
from cbfpy.config.cbf_config import CBFConfig
|
|
36
|
-
from cbfpy.utils.jax_utils import conditional_jit
|
|
37
36
|
from cbfpy.utils.general_utils import print_warning
|
|
38
37
|
|
|
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
38
|
|
|
45
39
|
@jax.tree_util.register_static
|
|
46
40
|
class CBF:
|
|
@@ -73,8 +67,8 @@ class CBF:
|
|
|
73
67
|
u_min: Optional[tuple],
|
|
74
68
|
u_max: Optional[tuple],
|
|
75
69
|
control_constrained: bool,
|
|
76
|
-
|
|
77
|
-
|
|
70
|
+
relax_qp: bool,
|
|
71
|
+
constraint_relaxation_penalties: tuple,
|
|
78
72
|
h_1: Callable[[ArrayLike], Array],
|
|
79
73
|
h_2: Callable[[ArrayLike], Array],
|
|
80
74
|
f: Callable[[ArrayLike], Array],
|
|
@@ -91,8 +85,8 @@ class CBF:
|
|
|
91
85
|
self.u_min = u_min
|
|
92
86
|
self.u_max = u_max
|
|
93
87
|
self.control_constrained = control_constrained
|
|
94
|
-
self.
|
|
95
|
-
self.
|
|
88
|
+
self.relax_qp = relax_qp
|
|
89
|
+
self.constraint_relaxation_penalties = constraint_relaxation_penalties
|
|
96
90
|
self.h_1 = h_1
|
|
97
91
|
self.h_2 = h_2
|
|
98
92
|
self.f = f
|
|
@@ -102,10 +96,6 @@ class CBF:
|
|
|
102
96
|
self.P_config = P
|
|
103
97
|
self.q_config = q
|
|
104
98
|
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
99
|
|
|
110
100
|
@classmethod
|
|
111
101
|
def from_config(cls, config: CBFConfig) -> "CBF":
|
|
@@ -124,8 +114,8 @@ class CBF:
|
|
|
124
114
|
config.u_min,
|
|
125
115
|
config.u_max,
|
|
126
116
|
config.control_constrained,
|
|
127
|
-
config.
|
|
128
|
-
config.
|
|
117
|
+
config.relax_qp,
|
|
118
|
+
config.constraint_relaxation_penalties,
|
|
129
119
|
config.h_1,
|
|
130
120
|
config.h_2,
|
|
131
121
|
config.f,
|
|
@@ -158,7 +148,7 @@ class CBF:
|
|
|
158
148
|
+ "Please provide an initial seed for these args in the config's init_args input"
|
|
159
149
|
)
|
|
160
150
|
|
|
161
|
-
@
|
|
151
|
+
@jax.jit
|
|
162
152
|
def safety_filter(self, z: Array, u_des: Array, *h_args) -> Array:
|
|
163
153
|
"""Apply the CBF safety filter to a nominal control
|
|
164
154
|
|
|
@@ -171,17 +161,17 @@ class CBF:
|
|
|
171
161
|
Array: Safe control input, shape (m,)
|
|
172
162
|
"""
|
|
173
163
|
P, q, A, b, G, h = self.qp_data(z, u_des, *h_args)
|
|
174
|
-
if self.
|
|
175
|
-
x_qp
|
|
164
|
+
if self.relax_qp:
|
|
165
|
+
x_qp = qpax.solve_qp_elastic_primal(
|
|
176
166
|
P,
|
|
177
167
|
q,
|
|
178
168
|
G,
|
|
179
169
|
h,
|
|
180
|
-
self.
|
|
170
|
+
penalty=jnp.asarray(self.constraint_relaxation_penalties),
|
|
181
171
|
solver_tol=self.solver_tol,
|
|
182
172
|
)
|
|
183
173
|
else:
|
|
184
|
-
x_qp, s_qp, z_qp, y_qp, converged, iters =
|
|
174
|
+
x_qp, s_qp, z_qp, y_qp, converged, iters = qpax.solve_qp(
|
|
185
175
|
P,
|
|
186
176
|
q,
|
|
187
177
|
A,
|
|
@@ -190,10 +180,6 @@ class CBF:
|
|
|
190
180
|
h,
|
|
191
181
|
solver_tol=self.solver_tol,
|
|
192
182
|
)
|
|
193
|
-
if DEBUG_SAFETY_FILTER:
|
|
194
|
-
print(
|
|
195
|
-
f"{'Converged' if converged else 'Did not converge'}. Iterations: {iters}"
|
|
196
|
-
)
|
|
197
183
|
return x_qp[: self.m]
|
|
198
184
|
|
|
199
185
|
def h(self, z: ArrayLike, *h_args) -> Array:
|
|
@@ -341,7 +327,6 @@ class CBF:
|
|
|
341
327
|
else:
|
|
342
328
|
return h
|
|
343
329
|
|
|
344
|
-
@conditional_jit(not DEBUG_QP_DATA)
|
|
345
330
|
def qp_data(
|
|
346
331
|
self, z: Array, u_des: Array, *h_args
|
|
347
332
|
) -> Tuple[Array, Array, Array, Array, Array, Array]:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
# Control Lyapunov Function / Control Barrier Functions (CLF-CBFs)
|
|
3
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,
|
|
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
5
|
based on a control objective defined by the CLF and a safety constraint defined by the CBF. Note that the CLF
|
|
6
6
|
objective should be quadratic and positive-definite to fit in this QP framework.
|
|
7
7
|
|
|
@@ -46,14 +46,8 @@ from jax.typing import ArrayLike
|
|
|
46
46
|
import qpax
|
|
47
47
|
|
|
48
48
|
from cbfpy.config.clf_cbf_config import CLFCBFConfig
|
|
49
|
-
from cbfpy.utils.jax_utils import conditional_jit
|
|
50
49
|
from cbfpy.utils.general_utils import print_warning
|
|
51
50
|
|
|
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
51
|
|
|
58
52
|
@jax.tree_util.register_static
|
|
59
53
|
class CLFCBF:
|
|
@@ -87,9 +81,9 @@ class CLFCBF:
|
|
|
87
81
|
u_min: Optional[tuple],
|
|
88
82
|
u_max: Optional[tuple],
|
|
89
83
|
control_constrained: bool,
|
|
90
|
-
|
|
91
|
-
cbf_relaxation_penalty: float,
|
|
84
|
+
relax_qp: bool,
|
|
92
85
|
clf_relaxation_penalty: float,
|
|
86
|
+
constraint_relaxation_penalties: tuple,
|
|
93
87
|
h_1: Callable[[ArrayLike], Array],
|
|
94
88
|
h_2: Callable[[ArrayLike], Array],
|
|
95
89
|
f: Callable[[ArrayLike], Array],
|
|
@@ -111,9 +105,9 @@ class CLFCBF:
|
|
|
111
105
|
self.u_min = u_min
|
|
112
106
|
self.u_max = u_max
|
|
113
107
|
self.control_constrained = control_constrained
|
|
114
|
-
self.
|
|
115
|
-
self.cbf_relaxation_penalty = cbf_relaxation_penalty
|
|
108
|
+
self.relax_qp = relax_qp
|
|
116
109
|
self.clf_relaxation_penalty = clf_relaxation_penalty
|
|
110
|
+
self.constraint_relaxation_penalties = constraint_relaxation_penalties
|
|
117
111
|
self.h_1 = h_1
|
|
118
112
|
self.h_2 = h_2
|
|
119
113
|
self.f = f
|
|
@@ -127,10 +121,6 @@ class CLFCBF:
|
|
|
127
121
|
self.H = H
|
|
128
122
|
self.F = F
|
|
129
123
|
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
124
|
|
|
135
125
|
@classmethod
|
|
136
126
|
def from_config(cls, config: CLFCBFConfig) -> "CLFCBF":
|
|
@@ -151,9 +141,9 @@ class CLFCBF:
|
|
|
151
141
|
config.u_min,
|
|
152
142
|
config.u_max,
|
|
153
143
|
config.control_constrained,
|
|
154
|
-
config.
|
|
155
|
-
config.cbf_relaxation_penalty,
|
|
144
|
+
config.relax_qp,
|
|
156
145
|
config.clf_relaxation_penalty,
|
|
146
|
+
config.constraint_relaxation_penalties,
|
|
157
147
|
config.h_1,
|
|
158
148
|
config.h_2,
|
|
159
149
|
config.f,
|
|
@@ -189,13 +179,13 @@ class CLFCBF:
|
|
|
189
179
|
"Cannot test Lgh; missing additional arguments.\n"
|
|
190
180
|
+ "Please provide an initial seed for these args in the config's init_args input"
|
|
191
181
|
)
|
|
192
|
-
test_lgv = self.LgV(test_z)
|
|
182
|
+
test_lgv = self.LgV(test_z, test_z)
|
|
193
183
|
if jnp.allclose(test_lgv, 0):
|
|
194
184
|
print_warning(
|
|
195
185
|
"LgV is zero. Consider increasing the relative degree or modifying the Lyapunov function."
|
|
196
186
|
)
|
|
197
187
|
|
|
198
|
-
@
|
|
188
|
+
@jax.jit
|
|
199
189
|
def controller(self, z: Array, z_des: Array, *h_args) -> Array:
|
|
200
190
|
"""Compute the CLF-CBF optimal control input, optimizing for the CLF objective while
|
|
201
191
|
satisfying the CBF safety constraint.
|
|
@@ -209,17 +199,17 @@ class CLFCBF:
|
|
|
209
199
|
Array: Safe control input, shape (m,)
|
|
210
200
|
"""
|
|
211
201
|
P, q, A, b, G, h = self.qp_data(z, z_des, *h_args)
|
|
212
|
-
if self.
|
|
213
|
-
x_qp
|
|
202
|
+
if self.relax_qp:
|
|
203
|
+
x_qp = qpax.solve_qp_elastic_primal(
|
|
214
204
|
P,
|
|
215
205
|
q,
|
|
216
206
|
G,
|
|
217
207
|
h,
|
|
218
|
-
self.
|
|
208
|
+
penalty=jnp.asarray(self.constraint_relaxation_penalties),
|
|
219
209
|
solver_tol=self.solver_tol,
|
|
220
210
|
)
|
|
221
211
|
else:
|
|
222
|
-
x_qp, s_qp, z_qp, y_qp, converged, iters =
|
|
212
|
+
x_qp, s_qp, z_qp, y_qp, converged, iters = qpax.solve_qp(
|
|
223
213
|
P,
|
|
224
214
|
q,
|
|
225
215
|
A,
|
|
@@ -228,10 +218,6 @@ class CLFCBF:
|
|
|
228
218
|
h,
|
|
229
219
|
solver_tol=self.solver_tol,
|
|
230
220
|
)
|
|
231
|
-
if DEBUG_CONTROLLER:
|
|
232
|
-
print(
|
|
233
|
-
f"{'Converged' if converged else 'Did not converge'}. Iterations: {iters}"
|
|
234
|
-
)
|
|
235
221
|
return x_qp[: self.m]
|
|
236
222
|
|
|
237
223
|
def h(self, z: ArrayLike, *h_args) -> Array:
|
|
@@ -300,49 +286,63 @@ class CLFCBF:
|
|
|
300
286
|
|
|
301
287
|
## CLF functions ##
|
|
302
288
|
|
|
303
|
-
def V(self, z: ArrayLike) -> Array:
|
|
289
|
+
def V(self, z: ArrayLike, z_des: ArrayLike) -> Array:
|
|
304
290
|
"""Control Lyapunov Function(s)
|
|
305
291
|
|
|
306
292
|
Args:
|
|
307
293
|
z (ArrayLike): State, shape (n,)
|
|
294
|
+
z_des (ArrayLike): Desired state, shape (n,)
|
|
308
295
|
|
|
309
296
|
Returns:
|
|
310
297
|
Array: CLF evaluation, shape (num_clf,)
|
|
311
298
|
"""
|
|
299
|
+
|
|
300
|
+
def _V_2(state):
|
|
301
|
+
return self.V_2(state, z_des)
|
|
302
|
+
|
|
312
303
|
# Take any relative-degree-2 CLFs and convert them to relative-degree-1
|
|
313
304
|
# NOTE: If adding args to the CLF, create a wrapper func like with the barrier function
|
|
314
|
-
V_2, dV_2_dt = jax.jvp(
|
|
305
|
+
V_2, dV_2_dt = jax.jvp(_V_2, (z,), (self.f(z),))
|
|
315
306
|
V2_rd1 = dV_2_dt + self.gamma_2(V_2)
|
|
316
307
|
|
|
317
308
|
# Merge the relative-degree-1 and relative-degree-2 CLFs
|
|
318
|
-
return jnp.concatenate([self.V_1(z), V2_rd1])
|
|
309
|
+
return jnp.concatenate([self.V_1(z, z_des), V2_rd1])
|
|
319
310
|
|
|
320
|
-
def V_and_LfV(self, z: ArrayLike) -> Tuple[Array, Array]:
|
|
311
|
+
def V_and_LfV(self, z: ArrayLike, z_des: ArrayLike) -> Tuple[Array, Array]:
|
|
321
312
|
"""Lie derivative of the CLF wrt the autonomous dynamics `f(z)`
|
|
322
313
|
|
|
323
314
|
The evaluation of the CLF is also returned "for free", a byproduct of the jacobian-vector-product
|
|
324
315
|
|
|
325
316
|
Args:
|
|
326
317
|
z (ArrayLike): State, shape (n,)
|
|
318
|
+
z_des (ArrayLike): Desired state, shape (n,)
|
|
327
319
|
|
|
328
320
|
Returns:
|
|
329
321
|
V (Array): CLF evaluation, shape (1,)
|
|
330
322
|
LfV (Array): Lie derivative of `V` w.r.t. `f`, shape (1,)
|
|
331
323
|
"""
|
|
332
|
-
return jax.jvp(self.V, (z,), (self.f(z),))
|
|
333
324
|
|
|
334
|
-
|
|
325
|
+
def _V(state):
|
|
326
|
+
return self.V(state, z_des)
|
|
327
|
+
|
|
328
|
+
return jax.jvp(_V, (z,), (self.f(z),))
|
|
329
|
+
|
|
330
|
+
def LgV(self, z: ArrayLike, z_des: ArrayLike) -> Array:
|
|
335
331
|
"""Lie derivative of the CLF wrt the control dynamics `g(z)u`
|
|
336
332
|
|
|
337
333
|
Args:
|
|
338
334
|
z (ArrayLike): State, shape (n,)
|
|
335
|
+
z_des (ArrayLike): Desired state, shape (n,)
|
|
339
336
|
|
|
340
337
|
Returns:
|
|
341
338
|
Array: LgV, shape (m,)
|
|
342
339
|
"""
|
|
343
340
|
|
|
341
|
+
def _V(state):
|
|
342
|
+
return self.V(state, z_des)
|
|
343
|
+
|
|
344
344
|
def _jvp(g_column):
|
|
345
|
-
return jax.jvp(
|
|
345
|
+
return jax.jvp(_V, (z,), (g_column,))[1]
|
|
346
346
|
|
|
347
347
|
return jax.vmap(_jvp, in_axes=1, out_axes=1)(self.g(z))
|
|
348
348
|
|
|
@@ -401,7 +401,7 @@ class CLFCBF:
|
|
|
401
401
|
"""
|
|
402
402
|
G = jnp.block(
|
|
403
403
|
[
|
|
404
|
-
[self.LgV(z), -1.0 * jnp.ones((self.num_clf, 1))],
|
|
404
|
+
[self.LgV(z, z_des), -1.0 * jnp.ones((self.num_clf, 1))],
|
|
405
405
|
[-self.Lgh(z, *h_args), jnp.zeros((self.num_cbf, 1))],
|
|
406
406
|
]
|
|
407
407
|
)
|
|
@@ -433,7 +433,7 @@ class CLFCBF:
|
|
|
433
433
|
Array: h vector, shape (num_constraints,)
|
|
434
434
|
"""
|
|
435
435
|
hz, lfh = self.h_and_Lfh(z, *h_args)
|
|
436
|
-
vz, lfv = self.V_and_LfV(z)
|
|
436
|
+
vz, lfv = self.V_and_LfV(z, z_des)
|
|
437
437
|
h = jnp.concatenate(
|
|
438
438
|
[
|
|
439
439
|
-lfv - self.gamma(vz),
|
|
@@ -447,7 +447,6 @@ class CLFCBF:
|
|
|
447
447
|
else:
|
|
448
448
|
return h
|
|
449
449
|
|
|
450
|
-
@conditional_jit(not DEBUG_QP_DATA)
|
|
451
450
|
def qp_data(
|
|
452
451
|
self, z: Array, z_des: Array, *h_args
|
|
453
452
|
) -> Tuple[Array, Array, Array, Array, Array, Array]:
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
|
|
4
4
|
## Defining the problem:
|
|
5
5
|
|
|
6
|
-
CBFs have two primary implementation requirements: the dynamics functions, and the barrier function(s).
|
|
6
|
+
CBFs have two primary implementation requirements: the dynamics functions, and the barrier function(s).
|
|
7
7
|
These can be specified through the `f`, `g`, and `h` methods, respectively. Note that the main requirements
|
|
8
8
|
for these functions are that (1) the dynamics are control-affine, and (2) the barrier function(s) are "zeroing"
|
|
9
9
|
barriers, as opposed to "reciprocal" barriers. A zeroing barrier is one which is positive in the interior of the
|
|
10
|
-
safe set, and zero on the boundary.
|
|
10
|
+
safe set, and zero on the boundary.
|
|
11
11
|
|
|
12
|
-
Depending on the relative degree of your barrier function(s), you should implement the `h_1` method
|
|
12
|
+
Depending on the relative degree of your barrier function(s), you should implement the `h_1` method
|
|
13
13
|
(for a relative-degree-1 barrier), and/or the `h_2` method (for a relative-degree-2 barrier).
|
|
14
14
|
|
|
15
15
|
## Tuning the CBF:
|
|
@@ -30,7 +30,7 @@ does require that P is positive semi-definite.
|
|
|
30
30
|
Depending on the construction of the barrier functions and if control limits are provided, the CBF QP may not always be
|
|
31
31
|
feasible. If allowing for relaxation in the CBFConfig, a slack variable will be introduced to ensure that the
|
|
32
32
|
problem is always feasible, with a high penalty on any infeasibility. This is generally useful for controller
|
|
33
|
-
robustness, but means that safety is not guaranteed.
|
|
33
|
+
robustness, but means that safety is not guaranteed.
|
|
34
34
|
|
|
35
35
|
If strict enforcement of the CBF is desired, your higest-level controller should handle the case where the QP
|
|
36
36
|
is infeasible.
|
|
@@ -38,6 +38,7 @@ is infeasible.
|
|
|
38
38
|
|
|
39
39
|
from typing import Optional, Callable
|
|
40
40
|
from abc import ABC, abstractmethod
|
|
41
|
+
import warnings
|
|
41
42
|
|
|
42
43
|
import numpy as np
|
|
43
44
|
import jax
|
|
@@ -67,9 +68,12 @@ class CBFConfig(ABC):
|
|
|
67
68
|
m (int): Control dimension
|
|
68
69
|
u_min (ArrayLike, optional): Minimum control input, shape (m,). Defaults to None (Unconstrained).
|
|
69
70
|
u_max (ArrayLike, optional): Maximum control input, shape (m,). Defaults to None (Unconstrained).
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
relax_qp (bool, optional): Whether to allow for relaxation in the CBF QP. Defaults to True.
|
|
72
|
+
Note: this is required for differentiability through the QP.
|
|
73
|
+
cbf_relaxation_penalty (float, optional): Penalty on the CBF slack variables in the relaxed QP.
|
|
74
|
+
Defaults to 1e3. Note: only applies if relax_qp is True.
|
|
75
|
+
control_relaxation_penalty (float, optional): Penalty on the control constraint slack variables in the
|
|
76
|
+
relaxed QP. Defaults to 1e5. Note: only applies if relax_qp is True.
|
|
73
77
|
solver_tol (float, optional): Tolerance for the QP solver. Defaults to 1e-3.
|
|
74
78
|
init_args (tuple, optional): If your barrier function relies on additional arguments other than just the state,
|
|
75
79
|
include an initial seed for these arguments here. This is to help test the output of the barrier function.
|
|
@@ -82,8 +86,9 @@ class CBFConfig(ABC):
|
|
|
82
86
|
m: int,
|
|
83
87
|
u_min: Optional[ArrayLike] = None,
|
|
84
88
|
u_max: Optional[ArrayLike] = None,
|
|
85
|
-
|
|
89
|
+
relax_qp: bool = True,
|
|
86
90
|
cbf_relaxation_penalty: float = 1e3,
|
|
91
|
+
control_relaxation_penalty: float = 1e5,
|
|
87
92
|
solver_tol: float = 1e-3,
|
|
88
93
|
init_args: tuple = (),
|
|
89
94
|
):
|
|
@@ -95,9 +100,9 @@ class CBFConfig(ABC):
|
|
|
95
100
|
raise ValueError(f"m must be a positive integer. Got: {m}")
|
|
96
101
|
self.m = m
|
|
97
102
|
|
|
98
|
-
if not isinstance(
|
|
99
|
-
raise ValueError(f"
|
|
100
|
-
self.
|
|
103
|
+
if not isinstance(relax_qp, bool):
|
|
104
|
+
raise ValueError(f"relax_qp must be a boolean. Got: {relax_qp}")
|
|
105
|
+
self.relax_qp = relax_qp
|
|
101
106
|
|
|
102
107
|
if not (
|
|
103
108
|
isinstance(cbf_relaxation_penalty, (int, float))
|
|
@@ -112,10 +117,25 @@ class CBFConfig(ABC):
|
|
|
112
117
|
raise ValueError(f"solver_tol must be a positive value. Got: {solver_tol}")
|
|
113
118
|
self.solver_tol = float(solver_tol)
|
|
114
119
|
|
|
120
|
+
if self.solver_tol > 1e-2:
|
|
121
|
+
print(
|
|
122
|
+
f"WARNING: solver tolerance is quite high ({self.solver_tol}). "
|
|
123
|
+
+ " Solution will likely be poor."
|
|
124
|
+
)
|
|
125
|
+
|
|
115
126
|
if not isinstance(init_args, tuple):
|
|
116
127
|
raise ValueError(f"init_args must be a tuple. Got: {init_args}")
|
|
117
128
|
self.init_args = init_args
|
|
118
129
|
|
|
130
|
+
if not (
|
|
131
|
+
isinstance(control_relaxation_penalty, (int, float))
|
|
132
|
+
and control_relaxation_penalty >= 0
|
|
133
|
+
):
|
|
134
|
+
raise ValueError(
|
|
135
|
+
f"control_relaxation_penalty must be a non-negative value. Got: {control_relaxation_penalty}"
|
|
136
|
+
)
|
|
137
|
+
self.control_relaxation_penalty = float(control_relaxation_penalty)
|
|
138
|
+
|
|
119
139
|
# Control limits require a bit of extra handling. They can be both None if unconstrained,
|
|
120
140
|
# but we should not have one limit as None and the other as some value
|
|
121
141
|
u_min = np.asarray(u_min, dtype=float).flatten() if u_min is not None else None
|
|
@@ -137,6 +157,12 @@ class CBFConfig(ABC):
|
|
|
137
157
|
self.u_min = u_min
|
|
138
158
|
self.u_max = u_max
|
|
139
159
|
|
|
160
|
+
if (
|
|
161
|
+
self.control_constrained
|
|
162
|
+
and self.control_relaxation_penalty < self.cbf_relaxation_penalty
|
|
163
|
+
):
|
|
164
|
+
print("WARNING: Control constraints have a lower penalty than the CBFs.")
|
|
165
|
+
|
|
140
166
|
# Test if the methods are provided and verify their output dimension
|
|
141
167
|
z_test = jnp.ones(self.n)
|
|
142
168
|
u_test = jnp.ones(self.m)
|
|
@@ -197,6 +223,25 @@ class CBFConfig(ABC):
|
|
|
197
223
|
if not self._is_symmetric_psd(P_test):
|
|
198
224
|
raise ValueError("P matrix must be symmetric positive semi-definite")
|
|
199
225
|
|
|
226
|
+
# Handle QP relaxation penalties, if relaxation is enabled
|
|
227
|
+
num_qp_constraints = (
|
|
228
|
+
self.num_cbf if not self.control_constrained else self.num_cbf + 2 * self.m
|
|
229
|
+
)
|
|
230
|
+
if self.control_constrained:
|
|
231
|
+
self.constraint_relaxation_penalties = tuple(
|
|
232
|
+
np.concatenate(
|
|
233
|
+
[
|
|
234
|
+
self.cbf_relaxation_penalty * np.ones(self.num_cbf),
|
|
235
|
+
self.control_relaxation_penalty * np.ones(2 * self.m),
|
|
236
|
+
]
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
else:
|
|
240
|
+
self.constraint_relaxation_penalties = tuple(
|
|
241
|
+
self.cbf_relaxation_penalty * np.ones(self.num_cbf)
|
|
242
|
+
)
|
|
243
|
+
assert len(self.constraint_relaxation_penalties) == num_qp_constraints
|
|
244
|
+
|
|
200
245
|
## Control Affine Dynamics ##
|
|
201
246
|
|
|
202
247
|
@abstractmethod
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
|
|
4
4
|
## Defining the problem:
|
|
5
5
|
|
|
6
|
-
As with the CBF, we require implementation of the dynamics functions `f` and `g`, as well as the barrier function(s)
|
|
6
|
+
As with the CBF, we require implementation of the dynamics functions `f` and `g`, as well as the barrier function(s)
|
|
7
7
|
`h`. Now, with the CLF-CBF, we require the definition of the Control Lyapunov Function (CLF) `V`. This CLF must be a
|
|
8
|
-
positive definite function of the state.
|
|
8
|
+
positive definite function of the state.
|
|
9
9
|
|
|
10
|
-
Depending on the relative degree of your barrier function(s), you should implement the `h_1` method
|
|
10
|
+
Depending on the relative degree of your barrier function(s), you should implement the `h_1` method
|
|
11
11
|
(for a relative-degree-1 barrier), and/or the `h_2` method (for a relative-degree-2 barrier).
|
|
12
12
|
|
|
13
13
|
Likewise, for the CLF, you should implement the `V_1` method (for a relative-degree-1 CLF), and/or the `V_2` method
|
|
@@ -24,7 +24,7 @@ CLF objective. These can be used to adjust the weightings between inputs, for in
|
|
|
24
24
|
|
|
25
25
|
## Relaxation:
|
|
26
26
|
|
|
27
|
-
If the CBF constraints are not necessarily globally feasible, you can enable further relaxation in the CLFCBFConfig.
|
|
27
|
+
If the CBF constraints are not necessarily globally feasible, you can enable further relaxation in the CLFCBFConfig.
|
|
28
28
|
However, since the CLF constraint was already relaxed with respect to the CBF constraint, this means that tuning the
|
|
29
29
|
relaxation parameters is critical. In general, the penalty on the CBF relaxation should be much higher than the penalty
|
|
30
30
|
on the CLF relaxation.
|
|
@@ -35,6 +35,7 @@ is infeasible.
|
|
|
35
35
|
|
|
36
36
|
from typing import Optional
|
|
37
37
|
|
|
38
|
+
import numpy as np
|
|
38
39
|
import jax.numpy as jnp
|
|
39
40
|
from jax import Array
|
|
40
41
|
from jax.typing import ArrayLike
|
|
@@ -66,10 +67,13 @@ class CLFCBFConfig(CBFConfig):
|
|
|
66
67
|
m (int): Control dimension
|
|
67
68
|
u_min (ArrayLike, optional): Minimum control input, shape (m,). Defaults to None (Unconstrained).
|
|
68
69
|
u_max (ArrayLike, optional): Maximum control input, shape (m,). Defaults to None (Unconstrained).
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
relax_qp (bool, optional): Whether to allow for relaxation in the CLF-CBF QP. Defaults to True.
|
|
71
|
+
Note: this is required for differentiability through the QP.
|
|
72
|
+
cbf_relaxation_penalty (float, optional): Penalty on the slack variable in the relaxed QP. Defaults to 1e4.
|
|
73
|
+
Note: only applies if relax_qp is True.
|
|
72
74
|
clf_relaxation_penalty (float): Penalty on the CLF slack variable when enforcing the CBF. Defaults to 1e2
|
|
75
|
+
control_relaxation_penalty (float, optional): Penalty on the control constraint slack variables in the
|
|
76
|
+
relaxed QP. Defaults to 1e5. Note: only applies if relax_qp is True.
|
|
73
77
|
solver_tol (float, optional): Tolerance for the QP solver. Defaults to 1e-3.
|
|
74
78
|
init_args (tuple, optional): If your barrier function relies on additional arguments other than just the state,
|
|
75
79
|
include an initial seed for these arguments here. This is to help test the output of the barrier function.
|
|
@@ -82,9 +86,10 @@ class CLFCBFConfig(CBFConfig):
|
|
|
82
86
|
m: int,
|
|
83
87
|
u_min: Optional[ArrayLike] = None,
|
|
84
88
|
u_max: Optional[ArrayLike] = None,
|
|
85
|
-
|
|
89
|
+
relax_qp: bool = True,
|
|
86
90
|
cbf_relaxation_penalty: float = 1e4,
|
|
87
91
|
clf_relaxation_penalty: float = 1e2,
|
|
92
|
+
control_relaxation_penalty: float = 1e5,
|
|
88
93
|
solver_tol: float = 1e-3,
|
|
89
94
|
init_args: tuple = (),
|
|
90
95
|
):
|
|
@@ -93,8 +98,9 @@ class CLFCBFConfig(CBFConfig):
|
|
|
93
98
|
m,
|
|
94
99
|
u_min,
|
|
95
100
|
u_max,
|
|
96
|
-
|
|
101
|
+
relax_qp,
|
|
97
102
|
cbf_relaxation_penalty,
|
|
103
|
+
control_relaxation_penalty,
|
|
98
104
|
solver_tol,
|
|
99
105
|
init_args,
|
|
100
106
|
)
|
|
@@ -108,10 +114,19 @@ class CLFCBFConfig(CBFConfig):
|
|
|
108
114
|
)
|
|
109
115
|
self.clf_relaxation_penalty = float(clf_relaxation_penalty)
|
|
110
116
|
|
|
117
|
+
# If relaxing the QP, need to have CLF penalty < CBF penalty < Control constraint penalty
|
|
118
|
+
if self.cbf_relaxation_penalty < self.clf_relaxation_penalty:
|
|
119
|
+
print("WARNING: CBF constraints have a lower penalty than the CLFs")
|
|
120
|
+
if (
|
|
121
|
+
self.control_constrained
|
|
122
|
+
and self.control_relaxation_penalty < self.clf_relaxation_penalty
|
|
123
|
+
):
|
|
124
|
+
print("WARNING: Control constraints have a lower penalty than the CLFs")
|
|
125
|
+
|
|
111
126
|
# Check on CLF dimension
|
|
112
127
|
z_test = jnp.ones(self.n)
|
|
113
|
-
v1_test = self.V_1(
|
|
114
|
-
v2_test = self.V_2(
|
|
128
|
+
v1_test = self.V_1(z_test, z_test)
|
|
129
|
+
v2_test = self.V_2(z_test, z_test)
|
|
115
130
|
if v1_test.ndim != 1 or v2_test.ndim != 1:
|
|
116
131
|
raise ValueError("CLF(s) must output 1D arrays")
|
|
117
132
|
self.num_rd1_clf = v1_test.shape[0]
|
|
@@ -144,9 +159,35 @@ class CLFCBFConfig(CBFConfig):
|
|
|
144
159
|
)
|
|
145
160
|
if not self._is_symmetric_psd(H_test):
|
|
146
161
|
raise ValueError("H(z) must be symmetric positive semi-definite")
|
|
147
|
-
# TODO: add a warning if the CLF relaxation penalty > the QP relaxation penalty?
|
|
148
162
|
|
|
149
|
-
|
|
163
|
+
# Handle QP relaxation penalties, if relaxation is enabled
|
|
164
|
+
num_qp_constraints = (
|
|
165
|
+
self.num_cbf + self.num_clf
|
|
166
|
+
if not self.control_constrained
|
|
167
|
+
else self.num_cbf + self.num_clf + 2 * self.m
|
|
168
|
+
)
|
|
169
|
+
if self.control_constrained:
|
|
170
|
+
self.constraint_relaxation_penalties = tuple(
|
|
171
|
+
np.concatenate(
|
|
172
|
+
[
|
|
173
|
+
self.clf_relaxation_penalty * np.ones(self.num_clf),
|
|
174
|
+
self.cbf_relaxation_penalty * np.ones(self.num_cbf),
|
|
175
|
+
self.control_relaxation_penalty * np.ones(2 * self.m),
|
|
176
|
+
]
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
else:
|
|
180
|
+
self.constraint_relaxation_penalties = tuple(
|
|
181
|
+
np.concatenate(
|
|
182
|
+
[
|
|
183
|
+
self.clf_relaxation_penalty * np.ones(self.num_clf),
|
|
184
|
+
self.cbf_relaxation_penalty * np.ones(self.num_cbf),
|
|
185
|
+
]
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
assert len(self.constraint_relaxation_penalties) == num_qp_constraints
|
|
189
|
+
|
|
190
|
+
def V_1(self, z: ArrayLike, z_des: ArrayLike) -> Array:
|
|
150
191
|
"""Relative-Degree-1 Control Lyapunov Function (CLF)
|
|
151
192
|
|
|
152
193
|
A CLF is a positive-definite function which evaluates to zero at the equilibrium point, and is
|
|
@@ -161,6 +202,7 @@ class CLFCBFConfig(CBFConfig):
|
|
|
161
202
|
|
|
162
203
|
Args:
|
|
163
204
|
z (ArrayLike): State, shape (n,)
|
|
205
|
+
z_des (ArrayLike): Desired state, shape (n,)
|
|
164
206
|
|
|
165
207
|
Returns:
|
|
166
208
|
Array: V(z): The RD1 CLF evaluation, shape (num_rd1_clf,)
|
|
@@ -168,7 +210,7 @@ class CLFCBFConfig(CBFConfig):
|
|
|
168
210
|
return jnp.array([])
|
|
169
211
|
|
|
170
212
|
# TODO: Check if the math behind this is actually valid
|
|
171
|
-
def V_2(self, z: ArrayLike) -> Array:
|
|
213
|
+
def V_2(self, z: ArrayLike, z_des: ArrayLike) -> Array:
|
|
172
214
|
"""Relative-Degree-2 (high-order) Control Lyapunov Function (CLF)
|
|
173
215
|
|
|
174
216
|
A CLF is a positive-definite function which evaluates to zero at the equilibrium point, and is
|
|
@@ -184,6 +226,7 @@ class CLFCBFConfig(CBFConfig):
|
|
|
184
226
|
|
|
185
227
|
Args:
|
|
186
228
|
z (ArrayLike): State, shape (n,)
|
|
229
|
+
z_des (ArrayLike): Desired state, shape (n,)
|
|
187
230
|
|
|
188
231
|
Returns:
|
|
189
232
|
Array: V(z): The RD2 CLF evaluation, shape (num_rd2_clf,)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
This is a convenient structure for building demo environments to test CBFs. However, it is not necessary to use this.
|
|
5
5
|
|
|
6
|
-
For instance, going back to the CBF usage pseudocode,
|
|
6
|
+
For instance, going back to the CBF usage pseudocode,
|
|
7
7
|
```
|
|
8
8
|
while True:
|
|
9
9
|
z = get_state()
|
|
@@ -11,10 +11,10 @@ while True:
|
|
|
11
11
|
u_nom = nominal_controller(z, z_des)
|
|
12
12
|
u = cbf.safety_filter(z, u_nom)
|
|
13
13
|
apply_control(u)
|
|
14
|
-
step()
|
|
14
|
+
step()
|
|
15
15
|
```
|
|
16
16
|
We use this base environment to set up the `get_state`, `get_desired_state`, `apply_control`, and `step` methods in
|
|
17
|
-
any derived environments.
|
|
17
|
+
any derived environments.
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
20
|
from abc import ABC, abstractmethod
|
|
@@ -49,10 +49,11 @@ class ACCConfig(CLFCBFConfig):
|
|
|
49
49
|
u_max=u_max,
|
|
50
50
|
# Note: Relaxing the CLF-CBF QP is tricky because there is an additional relaxation
|
|
51
51
|
# parameter already, balancing the CLF and CBF constraints.
|
|
52
|
-
|
|
53
|
-
# If indeed relaxing, ensure that the
|
|
52
|
+
relax_qp=False,
|
|
53
|
+
# If indeed relaxing, ensure that the CBF relaxation >> the CLF relaxation
|
|
54
54
|
clf_relaxation_penalty=10.0,
|
|
55
|
-
cbf_relaxation_penalty=
|
|
55
|
+
cbf_relaxation_penalty=1e5,
|
|
56
|
+
control_relaxation_penalty=1e6,
|
|
56
57
|
)
|
|
57
58
|
|
|
58
59
|
def drag_force(self, v: float) -> float:
|
|
@@ -82,7 +83,7 @@ class ACCConfig(CLFCBFConfig):
|
|
|
82
83
|
[D - self.T * v_f - 0.5 * (v_l - v_f) ** 2 / (self.cd * self.gravity)]
|
|
83
84
|
)
|
|
84
85
|
|
|
85
|
-
def V_1(self, z: ArrayLike) -> float:
|
|
86
|
+
def V_1(self, z: ArrayLike, z_des: ArrayLike) -> float:
|
|
86
87
|
# CLF: Squared error between the follower velocity and the desired velocity
|
|
87
88
|
return jnp.array([(z[0] - self.v_des) ** 2])
|
|
88
89
|
|
|
@@ -38,7 +38,7 @@ class DroneConfig(CBFConfig):
|
|
|
38
38
|
super().__init__(
|
|
39
39
|
n=6, # State = [position, velocity]
|
|
40
40
|
m=3, # Control = [velocity]
|
|
41
|
-
|
|
41
|
+
relax_qp=True,
|
|
42
42
|
init_args=(init_z_obs,),
|
|
43
43
|
cbf_relaxation_penalty=1e6,
|
|
44
44
|
)
|
|
@@ -73,7 +73,9 @@ class DroneConfig(CBFConfig):
|
|
|
73
73
|
- padding
|
|
74
74
|
]
|
|
75
75
|
)
|
|
76
|
-
h_box_containment = jnp.concatenate(
|
|
76
|
+
h_box_containment = jnp.concatenate(
|
|
77
|
+
[self.pos_max - z[:3], z[:3] - self.pos_min]
|
|
78
|
+
)
|
|
77
79
|
return jnp.concatenate([h_obstacle_avoidance, h_box_containment])
|
|
78
80
|
|
|
79
81
|
def alpha(self, h):
|
|
@@ -6,7 +6,7 @@ within the limits (+ some margin)
|
|
|
6
6
|
|
|
7
7
|
This uses a single-integrator reduced model of the manipulator dynamics.
|
|
8
8
|
We define the state as the joint positions and assume that we can directly control the joint velocities
|
|
9
|
-
i.e. z = [q1, q2, q3] and u = [q1_dot, q2_dot, q3_dot]
|
|
9
|
+
i.e. z = [q1, q2, q3] and u = [q1_dot, q2_dot, q3_dot]
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
import numpy as np
|
|
@@ -9,8 +9,8 @@ This demo is interactive: click and drag the obstacle to move it around.
|
|
|
9
9
|
Here, we have double integrator dynamics: z = [position, velocity], u = [acceleration]
|
|
10
10
|
and we also use the state of the obstacle as an input to the CBF: z_obs = [position, velocity]
|
|
11
11
|
|
|
12
|
-
This example includes both relative-degree-1 and relative-degree-2 CBFs. Staying inside the safe-set box is
|
|
13
|
-
RD2, since we have a positional barrier with acceleration inputs. Avoiding the obstacle is relative-degree-1,
|
|
12
|
+
This example includes both relative-degree-1 and relative-degree-2 CBFs. Staying inside the safe-set box is
|
|
13
|
+
RD2, since we have a positional barrier with acceleration inputs. Avoiding the obstacle is relative-degree-1,
|
|
14
14
|
because this is based on the relative velocity between the two objects.
|
|
15
15
|
"""
|
|
16
16
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: cbfpy
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: Control Barrier Functions in Python
|
|
5
5
|
Author-email: Daniel Morton <danielpmorton@gmail.com>
|
|
6
6
|
Project-URL: Documentation, https://danielpmorton.github.io/cbfpy/
|
|
@@ -8,15 +8,14 @@ Project-URL: Repository, https://github.com/danielpmorton/cbfpy/
|
|
|
8
8
|
Keywords: control,barrier,function,CBF,Jax
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
10
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
-
Classifier: Operating System :: OS Independent
|
|
12
11
|
Description-Content-Type: text/markdown
|
|
13
12
|
License-File: LICENSE
|
|
14
|
-
Requires-Dist: numpy
|
|
13
|
+
Requires-Dist: numpy
|
|
15
14
|
Requires-Dist: jax
|
|
16
15
|
Requires-Dist: jaxlib
|
|
17
16
|
Requires-Dist: qpax
|
|
18
17
|
Provides-Extra: examples
|
|
19
|
-
Requires-Dist: pybullet; extra == "examples"
|
|
18
|
+
Requires-Dist: pybullet>=3.2.7; extra == "examples"
|
|
20
19
|
Requires-Dist: pygame; extra == "examples"
|
|
21
20
|
Requires-Dist: wheel; extra == "examples"
|
|
22
21
|
Requires-Dist: matplotlib; extra == "examples"
|
|
@@ -26,12 +25,19 @@ Requires-Dist: mkdocstrings[python]; extra == "dev"
|
|
|
26
25
|
Requires-Dist: pylint; extra == "dev"
|
|
27
26
|
Requires-Dist: black; extra == "dev"
|
|
28
27
|
Provides-Extra: all
|
|
29
|
-
Requires-Dist:
|
|
30
|
-
Requires-Dist: black; extra == "all"
|
|
31
|
-
Requires-Dist: pybullet; extra == "all"
|
|
28
|
+
Requires-Dist: pybullet>=3.2.7; extra == "all"
|
|
32
29
|
Requires-Dist: pygame; extra == "all"
|
|
30
|
+
Requires-Dist: wheel; extra == "all"
|
|
31
|
+
Requires-Dist: matplotlib; extra == "all"
|
|
33
32
|
Requires-Dist: mkdocs-material; extra == "all"
|
|
34
33
|
Requires-Dist: mkdocstrings[python]; extra == "all"
|
|
34
|
+
Requires-Dist: pylint; extra == "all"
|
|
35
|
+
Requires-Dist: black; extra == "all"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
<div align="center">
|
|
39
|
+
<img src="https://github.com/user-attachments/assets/0304752c-cb75-4d53-b45f-b6b1a0912d9c" alt="logo"></img>
|
|
40
|
+
</div>
|
|
35
41
|
|
|
36
42
|
# CBFpy: Control Barrier Functions in Python and Jax
|
|
37
43
|
|
|
@@ -43,20 +49,20 @@ CBFpy is an easy-to-use and high-performance framework for constructing and solv
|
|
|
43
49
|
|
|
44
50
|
For API reference, see the following [documentation](https://danielpmorton.github.io/cbfpy)
|
|
45
51
|
|
|
46
|
-
If you use CBFpy in your research, please
|
|
52
|
+
If you use CBFpy in your research, please cite the following [paper](https://arxiv.org/abs/2503.06736):
|
|
47
53
|
|
|
48
54
|
```
|
|
49
|
-
@
|
|
50
|
-
author = {Morton, Daniel},
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
month = Dec,
|
|
56
|
-
year = {2024}
|
|
55
|
+
@article{morton2025oscbf,
|
|
56
|
+
author = {Morton, Daniel and Pavone, Marco},
|
|
57
|
+
title = {Safe, Task-Consistent Manipulation with Operational Space Control Barrier Functions},
|
|
58
|
+
year = {2025},
|
|
59
|
+
journal = {arXiv preprint arXiv:2503.06736},
|
|
60
|
+
note = {Accepted to IEEE/RSJ International Conference on Intelligent Robots and Systems (IROS), Hangzhou, 2025},
|
|
57
61
|
}
|
|
58
62
|
```
|
|
59
63
|
|
|
64
|
+
[](https://arxiv.org/abs/2503.06736)
|
|
65
|
+
|
|
60
66
|
## Installation
|
|
61
67
|
|
|
62
68
|
### From PyPI
|
|
@@ -25,7 +25,6 @@ cbfpy/examples/drone_demo.py
|
|
|
25
25
|
cbfpy/examples/joint_limits_demo.py
|
|
26
26
|
cbfpy/examples/point_robot_demo.py
|
|
27
27
|
cbfpy/examples/point_robot_obstacle_demo.py
|
|
28
|
-
cbfpy/temp/test_import.py
|
|
29
28
|
cbfpy/utils/__init__.py
|
|
30
29
|
cbfpy/utils/general_utils.py
|
|
31
30
|
cbfpy/utils/jax_utils.py
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
numpy
|
|
1
|
+
numpy
|
|
2
2
|
jax
|
|
3
3
|
jaxlib
|
|
4
4
|
qpax
|
|
5
5
|
|
|
6
6
|
[all]
|
|
7
|
-
|
|
8
|
-
black
|
|
9
|
-
pybullet
|
|
7
|
+
pybullet>=3.2.7
|
|
10
8
|
pygame
|
|
9
|
+
wheel
|
|
10
|
+
matplotlib
|
|
11
11
|
mkdocs-material
|
|
12
12
|
mkdocstrings[python]
|
|
13
|
+
pylint
|
|
14
|
+
black
|
|
13
15
|
|
|
14
16
|
[dev]
|
|
15
17
|
mkdocs-material
|
|
@@ -18,7 +20,7 @@ pylint
|
|
|
18
20
|
black
|
|
19
21
|
|
|
20
22
|
[examples]
|
|
21
|
-
pybullet
|
|
23
|
+
pybullet>=3.2.7
|
|
22
24
|
pygame
|
|
23
25
|
wheel
|
|
24
26
|
matplotlib
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cbfpy"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.3"
|
|
8
8
|
description = "Control Barrier Functions in Python"
|
|
9
9
|
authors = [{ name = "Daniel Morton", email = "danielpmorton@gmail.com" }]
|
|
10
10
|
readme = "README.md"
|
|
@@ -12,12 +12,11 @@ keywords = ["control", "barrier", "function", "CBF", "Jax"]
|
|
|
12
12
|
classifiers = [
|
|
13
13
|
"Programming Language :: Python :: 3",
|
|
14
14
|
"License :: OSI Approved :: MIT License",
|
|
15
|
-
"Operating System :: OS Independent",
|
|
16
15
|
]
|
|
17
16
|
dependencies = [
|
|
18
|
-
"numpy
|
|
19
|
-
"jax",
|
|
20
|
-
"jaxlib",
|
|
17
|
+
"numpy",
|
|
18
|
+
"jax", # 0.4.30 recommended for best CPU performance
|
|
19
|
+
"jaxlib", # 0.4.30 recommended for best CPU performance
|
|
21
20
|
"qpax",
|
|
22
21
|
]
|
|
23
22
|
|
|
@@ -27,7 +26,8 @@ Repository = "https://github.com/danielpmorton/cbfpy/"
|
|
|
27
26
|
|
|
28
27
|
[project.optional-dependencies]
|
|
29
28
|
examples = [
|
|
30
|
-
|
|
29
|
+
# Note: older versions of Pybullet have trouble with numpy 2.0
|
|
30
|
+
"pybullet>=3.2.7",
|
|
31
31
|
"pygame",
|
|
32
32
|
# "gym_pybullet_drones @ git+https://github.com/danielpmorton/gym-pybullet-drones.git",
|
|
33
33
|
"wheel",
|
|
@@ -35,13 +35,15 @@ examples = [
|
|
|
35
35
|
]
|
|
36
36
|
dev = ["mkdocs-material", "mkdocstrings[python]", "pylint", "black"]
|
|
37
37
|
all = [
|
|
38
|
-
"
|
|
39
|
-
"black",
|
|
40
|
-
"pybullet",
|
|
38
|
+
"pybullet>=3.2.7",
|
|
41
39
|
"pygame",
|
|
42
40
|
# "gym_pybullet_drones @ git+https://github.com/danielpmorton/gym-pybullet-drones.git",
|
|
41
|
+
"wheel",
|
|
42
|
+
"matplotlib",
|
|
43
43
|
"mkdocs-material",
|
|
44
44
|
"mkdocstrings[python]",
|
|
45
|
+
"pylint",
|
|
46
|
+
"black",
|
|
45
47
|
]
|
|
46
48
|
|
|
47
49
|
[tool.setuptools.packages.find]
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"""Speed tests for the CBF solver
|
|
2
2
|
|
|
3
3
|
We evaluate the speed of the solver NOT just via the QP solve but via the whole process
|
|
4
|
-
(solving for the nominal control input, constructing the QP matrices, and then solving).
|
|
4
|
+
(solving for the nominal control input, constructing the QP matrices, and then solving).
|
|
5
5
|
This provides a more accurate view of what the controller frequency would actually be if
|
|
6
6
|
deployed on the robot.
|
|
7
7
|
|
|
8
|
-
These test cases can also be used to check that modifications to the CBF implementation
|
|
8
|
+
These test cases can also be used to check that modifications to the CBF implementation
|
|
9
9
|
do not significantly degrade performance
|
|
10
10
|
"""
|
|
11
11
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|