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.
Files changed (38) hide show
  1. {cbfpy-0.0.1 → cbfpy-0.0.3}/PKG-INFO +23 -17
  2. {cbfpy-0.0.1 → cbfpy-0.0.3}/README.md +14 -10
  3. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/cbfs/cbf.py +11 -26
  4. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/cbfs/clf_cbf.py +36 -37
  5. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/config/cbf_config.py +56 -11
  6. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/config/clf_cbf_config.py +57 -14
  7. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/envs/base_env.py +3 -3
  8. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/envs/drone_env.py +1 -1
  9. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/examples/adaptive_cruise_control_demo.py +5 -4
  10. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/examples/drone_demo.py +4 -2
  11. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/examples/joint_limits_demo.py +1 -1
  12. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/examples/point_robot_obstacle_demo.py +2 -2
  13. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy.egg-info/PKG-INFO +23 -17
  14. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy.egg-info/SOURCES.txt +0 -1
  15. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy.egg-info/requires.txt +7 -5
  16. {cbfpy-0.0.1 → cbfpy-0.0.3}/pyproject.toml +11 -9
  17. {cbfpy-0.0.1 → cbfpy-0.0.3}/test/test_speed.py +2 -2
  18. cbfpy-0.0.1/cbfpy/temp/test_import.py +0 -3
  19. {cbfpy-0.0.1 → cbfpy-0.0.3}/LICENSE +0 -0
  20. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/__init__.py +0 -0
  21. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/cbfs/__init__.py +0 -0
  22. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/config/__init__.py +0 -0
  23. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/envs/__init__.py +0 -0
  24. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/envs/arm_envs.py +0 -0
  25. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/envs/car_env.py +0 -0
  26. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/envs/point_robot_envs.py +0 -0
  27. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/examples/__init__.py +0 -0
  28. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/examples/point_robot_demo.py +0 -0
  29. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/utils/__init__.py +0 -0
  30. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/utils/general_utils.py +0 -0
  31. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/utils/jax_utils.py +0 -0
  32. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/utils/math_utils.py +0 -0
  33. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy/utils/visualization.py +0 -0
  34. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy.egg-info/dependency_links.txt +0 -0
  35. {cbfpy-0.0.1 → cbfpy-0.0.3}/cbfpy.egg-info/top_level.txt +0 -0
  36. {cbfpy-0.0.1 → cbfpy-0.0.3}/setup.cfg +0 -0
  37. {cbfpy-0.0.1 → cbfpy-0.0.3}/test/__init__.py +0 -0
  38. {cbfpy-0.0.1 → cbfpy-0.0.3}/test/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: cbfpy
3
- Version: 0.0.1
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<2
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: pylint; extra == "all"
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 use the following citation:
52
+ If you use CBFpy in your research, please cite the following [paper](https://arxiv.org/abs/2503.06736):
47
53
 
48
54
  ```
49
- @software{Morton_CBFpy_2024,
50
- author = {Morton, Daniel},
51
- license = {MIT},
52
- title = {{CBFpy: Control Barrier Functions in Python and Jax}},
53
- url = {https://github.com/danielpmorton/cbfpy},
54
- version = {0.0.1},
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
+ [![Paper](http://img.shields.io/badge/arXiv-2503.06736-B31B1B.svg)](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 use the following citation:
15
+ If you use CBFpy in your research, please cite the following [paper](https://arxiv.org/abs/2503.06736):
12
16
 
13
17
  ```
14
- @software{Morton_CBFpy_2024,
15
- author = {Morton, Daniel},
16
- license = {MIT},
17
- title = {{CBFpy: Control Barrier Functions in Python and Jax}},
18
- url = {https://github.com/danielpmorton/cbfpy},
19
- version = {0.0.1},
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
+ [![Paper](http://img.shields.io/badge/arXiv-2503.06736-B31B1B.svg)](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
- ![Image: Quadrotor avoiding an obstacle](https://raw.githubusercontent.com/danielpmorton/cbfpy/refs/heads/main/images/drone_demo.gif)
195
+ ![Image: Quadrotor avoiding an obstacle](https://raw.githubusercontent.com/danielpmorton/cbfpy/refs/heads/main/images/drone_demo.gif)
@@ -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
- relax_cbf: bool,
77
- cbf_relaxation_penalty: float,
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.relax_cbf = relax_cbf
95
- self.cbf_relaxation_penalty = cbf_relaxation_penalty
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.relax_cbf,
128
- config.cbf_relaxation_penalty,
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
- @conditional_jit(not DEBUG_SAFETY_FILTER)
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.relax_cbf:
175
- x_qp, t_qp, s1_qp, s2_qp, z1_qp, z2_qp, converged, iters = self.qp_solver(
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.cbf_relaxation_penalty,
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 = self.qp_solver(
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
- relax_cbf: bool,
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.relax_cbf = relax_cbf
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.relax_cbf,
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
- @conditional_jit(not DEBUG_CONTROLLER)
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.relax_cbf:
213
- x_qp, t_qp, s1_qp, s2_qp, z1_qp, z2_qp, converged, iters = self.qp_solver(
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.cbf_relaxation_penalty,
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 = self.qp_solver(
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(self.V_2, (z,), (self.f(z),))
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
- def LgV(self, z: ArrayLike) -> Array:
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(self.V, (z,), (g_column,))[1]
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
- relax_cbf (bool, optional): Whether to allow for relaxation in the CBF QP. Defaults to True.
71
- cbf_relaxation_penalty (float, optional): Penalty on the slack variable in the relaxed CBF QP. Defaults to 1e3.
72
- Note: only applies if relax_cbf is True.
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
- relax_cbf: bool = True,
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(relax_cbf, bool):
99
- raise ValueError(f"relax_cbf must be a boolean. Got: {relax_cbf}")
100
- self.relax_cbf = relax_cbf
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
- relax_cbf (bool, optional): Whether to allow for relaxation in the CBF QP. Defaults to True.
70
- cbf_relaxation_penalty (float, optional): Penalty on the slack variable in the relaxed CBF QP. Defaults to 1e4.
71
- Note: only applies if relax_cbf is True.
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
- relax_cbf: bool = True,
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
- relax_cbf,
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(jnp.ones(self.n))
114
- v2_test = self.V_2(jnp.ones(self.n))
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
- def V_1(self, z: ArrayLike) -> Array:
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
@@ -1,5 +1,5 @@
1
1
  """
2
- # Drone Environment
2
+ # Drone Environment
3
3
 
4
4
  This is a wrapper around the gym-pybullet-drones environment, using velocity control.
5
5
  """
@@ -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
- relax_cbf=False,
53
- # If indeed relaxing, ensure that the QP relaxation >> the CLF relaxation
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=1e6,
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
- relax_cbf=True,
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([self.pos_max - z[:3], z[:3] - self.pos_min])
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
1
+ Metadata-Version: 2.4
2
2
  Name: cbfpy
3
- Version: 0.0.1
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<2
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: pylint; extra == "all"
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 use the following citation:
52
+ If you use CBFpy in your research, please cite the following [paper](https://arxiv.org/abs/2503.06736):
47
53
 
48
54
  ```
49
- @software{Morton_CBFpy_2024,
50
- author = {Morton, Daniel},
51
- license = {MIT},
52
- title = {{CBFpy: Control Barrier Functions in Python and Jax}},
53
- url = {https://github.com/danielpmorton/cbfpy},
54
- version = {0.0.1},
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
+ [![Paper](http://img.shields.io/badge/arXiv-2503.06736-B31B1B.svg)](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<2
1
+ numpy
2
2
  jax
3
3
  jaxlib
4
4
  qpax
5
5
 
6
6
  [all]
7
- pylint
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.1"
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<2", # Pybullet has trouble with numpy 2.0
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
- "pybullet",
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
- "pylint",
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
 
@@ -1,3 +0,0 @@
1
- import cbfpy
2
-
3
- cbfpy.CBF()
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