Skip to content

Commit

Permalink
Add state equality constraint to ellipsoid containment. (#37)
Browse files Browse the repository at this point in the history
When we constrain that the compatible region contains the inner ellipsoid, also impose the state equality constraints.
  • Loading branch information
hongkai-dai authored Jan 20, 2024
1 parent 12c4b42 commit db913ea
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 12 deletions.
26 changes: 20 additions & 6 deletions compatible_clf_cbf/clf_cbf.py
Original file line number Diff line number Diff line change
Expand Up @@ -950,7 +950,7 @@ def _find_max_inner_ellipsoid(
V_contain_lagrangian.add_constraint(
prog,
inner_ineq_poly=np.array([ellipsoid]),
inner_eq_poly=np.array([]),
inner_eq_poly=self.state_eq_constraints,
outer_poly=V - rho,
)
b_contain_lagrangians = [
Expand All @@ -962,7 +962,7 @@ def _find_max_inner_ellipsoid(
b_contain_lagrangians[i].add_constraint(
prog,
inner_ineq_poly=np.array([ellipsoid]),
inner_eq_poly=np.array([]),
inner_eq_poly=self.state_eq_constraints,
outer_poly=-b[i],
)

Expand Down Expand Up @@ -1028,8 +1028,15 @@ def _add_ellipsoid_in_compatible_region_constraint(
)
if V is not None:
V_degree = V.TotalDegree()
inner_eq_lagrangian_degree = (
[]
if self.state_eq_constraints is None
else [
V_degree - poly.TotalDegree() for poly in self.state_eq_constraints
]
)
ellipsoid_in_V_lagrangian_degree = ContainmentLagrangianDegree(
inner_ineq=[V_degree - 2], inner_eq=[], outer=-1
inner_ineq=[V_degree - 2], inner_eq=inner_eq_lagrangian_degree, outer=-1
)
ellipsoid_in_V_lagrangian = (
ellipsoid_in_V_lagrangian_degree.construct_lagrangian(prog, self.x_set)
Expand All @@ -1038,20 +1045,27 @@ def _add_ellipsoid_in_compatible_region_constraint(
ellipsoid_in_V_lagrangian.add_constraint(
prog,
inner_ineq_poly=np.array([ellipsoid_poly]),
inner_eq_poly=np.array([]),
inner_eq_poly=self.state_eq_constraints,
outer_poly=V - sym.Polynomial({sym.Monomial(): sym.Expression(rho)}),
)
for i in range(b.size):
b_degree = b[i].TotalDegree()
inner_eq_lagrangian_degree = (
[]
if self.state_eq_constraints is None
else [
b_degree - poly.TotalDegree() for poly in self.state_eq_constraints
]
)
ellipsoid_in_b_lagrangian_degree = ContainmentLagrangianDegree(
inner_ineq=[b_degree - 2], inner_eq=[], outer=-1
inner_ineq=[b_degree - 2], inner_eq=inner_eq_lagrangian_degree, outer=-1
)
ellipsoid_in_b_lagrangian = (
ellipsoid_in_b_lagrangian_degree.construct_lagrangian(prog, self.x_set)
)
ellipsoid_in_b_lagrangian.add_constraint(
prog,
inner_ineq_poly=np.array([ellipsoid_poly]),
inner_eq_poly=np.array([]),
inner_eq_poly=self.state_eq_constraints,
outer_poly=-b[i],
)
13 changes: 8 additions & 5 deletions compatible_clf_cbf/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,14 +150,17 @@ def add_constraint(
self,
prog,
inner_ineq_poly: np.ndarray,
inner_eq_poly: np.ndarray,
inner_eq_poly: Optional[np.ndarray],
outer_poly: sym.Polynomial,
) -> Tuple[sym.Polynomial, np.ndarray]:
return prog.AddSosConstraint(
-(1 + self.outer) * outer_poly
+ self.inner_ineq.dot(inner_ineq_poly)
+ self.inner_eq.dot(inner_eq_poly)
sos_condition = -(1 + self.outer) * outer_poly + self.inner_ineq.dot(
inner_ineq_poly
)
if inner_eq_poly is None:
assert self.inner_eq is None or self.inner_eq.size == 0
else:
sos_condition += self.inner_eq.dot(inner_eq_poly)
return prog.AddSosConstraint(sos_condition)

def get_result(self, result: solvers.MathematicalProgramResult) -> Self:
return ContainmentLagrangian(
Expand Down
124 changes: 124 additions & 0 deletions tests/test_clf_cbf.py
Original file line number Diff line number Diff line change
Expand Up @@ -730,3 +730,127 @@ def test_binary_search_clf_cbf_given_lagrangian(self):
# with the i'th unsafe region.
x_samples = 5 * np.random.randn(1000, 2) - np.array([[5, 0]])
self.check_unsafe_region_by_sample(b, x_samples)


class TestClfCbfWStateEqConstraints:
"""
Test finding CLF/CBF for system with equality constraints on the state.
The system dynamics is
ẋ₀ = (x₁+1)u,
ẋ₁ = −x₀u,
ẋ₂ = −x₀−u
with the constraints x₀² + (x₁+1)² = 1
"""

@classmethod
def setup_class(cls):
cls.nx = 3
cls.nu = 1
cls.x = sym.MakeVectorContinuousVariable(3, "x")
cls.f = np.array(
[sym.Polynomial(), sym.Polynomial(), sym.Polynomial(-cls.x[0])]
)
cls.g = np.array(
[
[sym.Polynomial(cls.x[1] + 1)],
[sym.Polynomial(-cls.x[0])],
[sym.Polynomial(-1)],
]
)
cls.unsafe_regions = [
np.array([sym.Polynomial(cls.x[0] + cls.x[1] + cls.x[2] + 3)])
]
cls.state_eq_constraints = np.array(
[sym.Polynomial(cls.x[0] ** 2 + cls.x[1] ** 2 + 2 * cls.x[1])]
)
cls.kappa_V = 0.001
cls.kappa_b = np.array([cls.kappa_V])
cls.barrier_eps = np.array([0.01])

def search_lagrangians(
self, check_result=False
) -> Tuple[
mut.CompatibleClfCbf,
mut.CompatibleLagrangians,
List[mut.UnsafeRegionLagrangians],
sym.Polynomial,
np.ndarray,
float,
]:
use_y_squared = True
dut = mut.CompatibleClfCbf(
f=self.f,
g=self.g,
x=self.x,
unsafe_regions=self.unsafe_regions,
Au=None,
bu=None,
with_clf=True,
use_y_squared=use_y_squared,
state_eq_constraints=self.state_eq_constraints,
)
V_init = sym.Polynomial(self.x[0] ** 2 + self.x[1] ** 2 + self.x[2] ** 2)
b_init = np.array(
[sym.Polynomial(0.001 - self.x[0] ** 2 - self.x[1] ** 2 - self.x[2] ** 2)]
)

lagrangian_degrees = mut.CompatibleLagrangianDegrees(
lambda_y=[mut.CompatibleLagrangianDegrees.Degree(x=2, y=0)],
xi_y=mut.CompatibleLagrangianDegrees.Degree(x=2, y=0),
y=None,
rho_minus_V=mut.CompatibleLagrangianDegrees.Degree(x=2, y=2),
b_plus_eps=[mut.CompatibleLagrangianDegrees.Degree(x=2, y=2)],
state_eq_constraints=[mut.CompatibleLagrangianDegrees.Degree(x=2, y=2)],
)
rho = 0.01

(
compatible_prog,
compatible_lagrangians,
) = dut.construct_search_compatible_lagrangians(
V_init,
b_init,
self.kappa_V,
self.kappa_b,
lagrangian_degrees,
rho,
self.barrier_eps,
)
solver_options = solvers.SolverOptions()
solver_options.SetOption(solvers.CommonSolverOption.kPrintToConsole, 0)
compatible_result = solvers.Solve(compatible_prog, None, solver_options)
assert compatible_result.is_success()
compatible_lagrangians_result = compatible_lagrangians.get_result(
compatible_result, coefficient_tol=1e-5
)

unsafe_lagrangians = [
dut.certify_cbf_unsafe_region(
unsafe_region_index=0,
cbf=b_init[0],
cbf_lagrangian_degree=0,
unsafe_region_lagrangian_degrees=[0],
state_eq_constraints_lagrangian_degrees=[0],
solver_options=None,
)
]
if check_result:
assert utils.is_sos(
-(1 + unsafe_lagrangians[0].cbf) * b_init[0]
+ unsafe_lagrangians[0].unsafe_region.dot(self.unsafe_regions[0])
- unsafe_lagrangians[0].state_eq_constraints.dot(
self.state_eq_constraints
)
)
return (
dut,
compatible_lagrangians_result,
unsafe_lagrangians,
V_init,
b_init,
rho,
)

def test_search_lagrangians(self):
self.search_lagrangians(check_result=True)
2 changes: 1 addition & 1 deletion tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def test_add_constraint2(self, inner_ineq_degree, outer_degree):
)
lagrangians = containment_lagrangian_degree.construct_lagrangian(prog, x_set)
lagrangians.add_constraint(
prog, inner_ineq_poly, inner_eq_poly=np.array([]), outer_poly=outer_poly
prog, inner_ineq_poly, inner_eq_poly=None, outer_poly=outer_poly
)
result = solvers.Solve(prog)
assert not result.is_success()
Expand Down

0 comments on commit db913ea

Please sign in to comment.