Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the cross term y[i]*y[j]>=0 to P-satz. #76

Merged
merged 1 commit into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions compatible_clf_cbf/clf_cbf.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ class CompatibleLagrangians:
# The Lagrangian polynomial multiplies with y if use_y_squared = False.
# This multiplier is an array of SOS polynomials.
y: Optional[np.ndarray]
# The Lagrangian polynomial multiplies with cross terms of y
# (namely y[i]*y[j]) if use_y_squared = False. This multiplier is an array
# of SOS poynomials.
y_cross: Optional[np.ndarray]
# The Lagrangian polynomial multiplies with ρ − V when with_clf = True, and
# we search for an CLF with a region-of-attraction {x | V(x) <= ρ}.
# Should be a SOS polynomial.
Expand All @@ -76,6 +80,11 @@ def get_result(
if self.y is not None
else None
)
y_cross_result = (
get_polynomial_result(result, self.y_cross, coefficient_tol)
if self.y_cross is not None
else None
)
rho_minus_V_result = (
get_polynomial_result(result, self.rho_minus_V, coefficient_tol)
if self.rho_minus_V is not None
Expand All @@ -93,6 +102,7 @@ def get_result(
lambda_y=lambda_y_result,
xi_y=xi_y_result,
y=y_result,
y_cross=y_cross_result,
rho_minus_V=rho_minus_V_result,
h_plus_eps=h_plus_eps_result,
state_eq_constraints=state_eq_constraints_result,
Expand Down Expand Up @@ -192,6 +202,7 @@ def __init__(self, *args, **kwargs):
lambda_y: List[XYDegree]
xi_y: XYDegree
y: Optional[List[XYDegree]]
y_cross: Optional[List[XYDegree]]
rho_minus_V: Optional[XYDegree]
h_plus_eps: List[XYDegree]
state_eq_constraints: Optional[List[XYDegree]]
Expand All @@ -206,6 +217,7 @@ def to_lagrangians(
lambda_y_lagrangian: Optional[np.ndarray] = None,
xi_y_lagrangian: Optional[sym.Polynomial] = None,
y_lagrangian: Optional[np.ndarray] = None,
y_cross_lagrangian: Optional[np.ndarray] = None,
rho_minus_V_lagrangian: Optional[sym.Polynomial] = None,
h_plus_eps_lagrangian: Optional[np.ndarray] = None,
state_eq_constraints_lagrangian: Optional[np.ndarray] = None,
Expand All @@ -231,6 +243,15 @@ def to_lagrangians(
y_lagrangian_new = _to_lagrangian_impl(
prog, x, y, sos_type, is_sos=True, degree=self.y, lagrangian=y_lagrangian
)
y_cross_lagrangian_new = _to_lagrangian_impl(
prog,
x,
y,
sos_type,
is_sos=True,
degree=self.y_cross,
lagrangian=y_cross_lagrangian,
)
rho_minus_V = _to_lagrangian_impl(
prog,
x,
Expand Down Expand Up @@ -262,6 +283,7 @@ def to_lagrangians(
lambda_y=lambda_y,
xi_y=xi_y,
y=y_lagrangian_new,
y_cross=y_cross_lagrangian_new,
rho_minus_V=rho_minus_V,
h_plus_eps=h_plus_eps,
state_eq_constraints=state_eq_constraints,
Expand All @@ -288,6 +310,10 @@ class CompatibleWVrepLagrangians:
# this is None.
# Size is (num_y,)
y: Optional[np.ndarray]
# The SOS Lagrangian multiplier multiplies with cross terms of y
# (namely y[i] * y[j]). When use_y_square=True, this is None. Size is
# (num_y * (num_y-1)/2,)
y_cross: Optional[np.ndarray]
# The SOS lagrangian multiplier multiplies with (1-V). If we don't search
# for CLF, then this multiplier is None.
rho_minus_V: Optional[sym.Polynomial]
Expand Down Expand Up @@ -321,6 +347,11 @@ def get_result(
if self.y is None
else get_polynomial_result(result, self.y, coefficient_tol)
)
y_cross = (
None
if self.y_cross is None
else get_polynomial_result(result, self.y_cross, coefficient_tol)
)
rho_minus_V = (
None
if self.rho_minus_V is None
Expand All @@ -339,6 +370,7 @@ def get_result(
u_extreme_rays,
xi_y,
y,
y_cross,
rho_minus_V,
h_plus_eps,
state_eq_constraints,
Expand All @@ -351,6 +383,7 @@ class CompatibleWVrepLagrangianDegrees:
u_extreme_rays: Optional[List[XYDegree]]
xi_y: Optional[XYDegree]
y: Optional[List[XYDegree]]
y_cross: Optional[List[XYDegree]]
rho_minus_V: Optional[XYDegree]
h_plus_eps: List[XYDegree]
state_eq_constraints: Optional[List[XYDegree]]
Expand All @@ -366,6 +399,7 @@ def to_lagrangians(
u_extreme_rays_lagrangian: Optional[np.ndarray] = None,
xi_y_lagrangian: Optional[sym.Polynomial] = None,
y_lagrangian: Optional[np.ndarray] = None,
y_cross_lagrangian: Optional[np.ndarray] = None,
rho_minus_V_lagrangian: Optional[sym.Polynomial] = None,
h_plus_eps_lagrangian: Optional[np.ndarray] = None,
state_eq_constraints_lagrangian: Optional[np.ndarray] = None,
Expand Down Expand Up @@ -407,6 +441,15 @@ def to_lagrangians(
degree=self.y,
lagrangian=y_lagrangian,
),
y_cross=_to_lagrangian_impl(
prog,
x,
y,
sos_type,
is_sos=True,
degree=self.y_cross,
lagrangian=y_cross_lagrangian,
),
rho_minus_V=_to_lagrangian_impl(
prog,
x,
Expand Down Expand Up @@ -987,6 +1030,17 @@ def __init__(
self.y_poly = np.array(
[sym.Polynomial(sym.Monomial(self.y[i], 1)) for i in range(y_size)]
)
# y_cross_poly contains the cross terms y[i]*y[j].
self.y_cross_poly = np.empty(
int(self.y.size * (self.y.size - 1) / 2), dtype=object
)
y_cross_count = 0
for i in range(self.y.size):
for j in range(i + 1, self.y.size):
self.y_cross_poly[y_cross_count] = sym.Polynomial(
sym.Monomial({self.y[i]: 1, self.y[j]: 1})
)
y_cross_count += 1
# y_squared_poly[i] is just the polynomial y[i]**2.
self.y_squared_poly = np.array(
[sym.Polynomial(sym.Monomial(self.y[i], 2)) for i in range(y_size)]
Expand Down Expand Up @@ -1809,6 +1863,8 @@ def _add_compatibility(
if not self.use_y_squared:
assert lagrangians.y is not None
poly -= lagrangians.y.dot(self.y_poly)
if lagrangians.y_cross is not None:
poly -= lagrangians.y_cross.dot(self.y_cross_poly)

# Compute s₃(x, y)(1 − V)
if self.with_clf and local_clf:
Expand Down Expand Up @@ -1887,6 +1943,8 @@ def _add_compatibility_w_vrep(
if not self.use_y_squared:
assert lagrangians.y is not None
poly -= lagrangians.y.dot(self.y_poly)
if lagrangians.y_cross is not None:
poly -= lagrangians.y_cross.dot(self.y_cross_poly)

if self.with_clf and local_clf:
assert lagrangians.rho_minus_V is not None
Expand Down
1 change: 1 addition & 0 deletions examples/linear_toy/linear_toy_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def search_compatible_lagrangians(
if dut.use_y_squared
else [clf_cbf.XYDegree(x=2, y=0) for _ in range(y_size)]
),
y_cross=None,
rho_minus_V=None,
h_plus_eps=[clf_cbf.XYDegree(x=2, y=0)],
state_eq_constraints=None,
Expand Down
1 change: 1 addition & 0 deletions examples/linear_toy/linear_toy_w_input_limits_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def search_compatible_lagrangians(
if dut.use_y_squared
else [clf_cbf.XYDegree(x=2, y=0) for _ in range(y_size)]
),
y_cross=None,
rho_minus_V=clf_cbf.XYDegree(x=2, y=2),
h_plus_eps=[clf_cbf.XYDegree(x=2, y=2)],
state_eq_constraints=None,
Expand Down
1 change: 1 addition & 0 deletions examples/nonlinear_toy/demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def main(use_y_squared: bool, with_u_bound: bool):
if use_y_squared
else [clf_cbf.XYDegree(x=4, y=0) for _ in range(compatible.y.size)]
),
y_cross=None,
rho_minus_V=clf_cbf.XYDegree(x=2, y=2),
h_plus_eps=[clf_cbf.XYDegree(x=2, y=2)],
state_eq_constraints=None,
Expand Down
2 changes: 2 additions & 0 deletions examples/nonlinear_toy/demo_trigpoly.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ def search(use_v_rep: bool, unit_test_flag: bool = False):
if use_y_squared
else [clf_cbf.XYDegree(x=4, y=0) for _ in range(compatible.y.size)]
),
y_cross=None,
rho_minus_V=clf_cbf.XYDegree(x=2, y=2 if use_y_squared else 0),
h_plus_eps=[clf_cbf.XYDegree(x=2, y=2 if use_y_squared else 0)],
state_eq_constraints=[clf_cbf.XYDegree(x=4, y=2 if use_y_squared else 1)],
Expand All @@ -195,6 +196,7 @@ def search(use_v_rep: bool, unit_test_flag: bool = False):
if use_y_squared
else [clf_cbf.XYDegree(x=4, y=0) for _ in range(compatible.y.size)]
),
y_cross=None,
rho_minus_V=clf_cbf.XYDegree(x=2, y=2 if use_y_squared else 0),
h_plus_eps=[clf_cbf.XYDegree(x=2, y=2 if use_y_squared else 0)],
state_eq_constraints=[clf_cbf.XYDegree(x=2, y=2 if use_y_squared else 1)],
Expand Down
1 change: 1 addition & 0 deletions examples/nonlinear_toy/synthesize_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def main(with_u_bound: bool):
lambda_y=[clf_cbf.XYDegree(x=3, y=0)],
xi_y=clf_cbf.XYDegree(x=2, y=0),
y=None,
y_cross=None,
rho_minus_V=clf_cbf.XYDegree(x=2, y=2),
h_plus_eps=[clf_cbf.XYDegree(x=2, y=2)],
state_eq_constraints=None,
Expand Down
1 change: 1 addition & 0 deletions examples/power_converter/demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ def search(use_y_squared: bool):
if use_y_squared
else [clf_cbf.XYDegree(x=4, y=0) for _ in range(compatible.y.size)]
),
y_cross=None,
rho_minus_V=clf_cbf.XYDegree(x=4, y=2),
h_plus_eps=[clf_cbf.XYDegree(x=4, y=2)],
state_eq_constraints=None,
Expand Down
1 change: 1 addition & 0 deletions examples/quadrotor/demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ def search(use_y_squared: bool, with_u_bound: bool):
if use_y_squared
else [clf_cbf.XYDegree(x=4, y=0) for _ in range(compatible.y.size)]
),
y_cross=None,
rho_minus_V=clf_cbf.XYDegree(x=2, y=2),
h_plus_eps=[clf_cbf.XYDegree(x=2, y=2)],
state_eq_constraints=[clf_cbf.XYDegree(x=2, y=2)],
Expand Down
2 changes: 2 additions & 0 deletions examples/quadrotor2d/demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ def main(use_y_squared: bool, with_u_bound: bool, use_v_rep: bool):
if use_y_squared
else [clf_cbf.XYDegree(x=4, y=2) for _ in range(compatible.y.size)]
),
y_cross=None,
rho_minus_V=clf_cbf.XYDegree(x=2, y=2),
h_plus_eps=[clf_cbf.XYDegree(x=2, y=2)],
state_eq_constraints=[clf_cbf.XYDegree(x=4, y=2)],
Expand All @@ -97,6 +98,7 @@ def main(use_y_squared: bool, with_u_bound: bool, use_v_rep: bool):
if use_y_squared
else [clf_cbf.XYDegree(x=4, y=0) for _ in range(compatible.y.size)]
),
y_cross=None,
rho_minus_V=clf_cbf.XYDegree(x=2, y=2),
h_plus_eps=[clf_cbf.XYDegree(x=2, y=2)],
state_eq_constraints=[clf_cbf.XYDegree(x=2, y=2)],
Expand Down
2 changes: 2 additions & 0 deletions examples/quadrotor2d/demo_taylor.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ def search_clf_cbf(
if use_y_squared
else [clf_cbf.XYDegree(x=4, y=0) for _ in range(compatible.y.size)]
),
y_cross=None,
rho_minus_V=clf_cbf.XYDegree(x=4, y=2),
h_plus_eps=[clf_cbf.XYDegree(x=4, y=2)],
state_eq_constraints=None,
Expand All @@ -106,6 +107,7 @@ def search_clf_cbf(
if use_y_squared
else [clf_cbf.XYDegree(x=4, y=0) for _ in range(compatible.y.size)]
),
y_cross=None,
rho_minus_V=clf_cbf.XYDegree(x=4, y=2),
h_plus_eps=[clf_cbf.XYDegree(x=4, y=2)],
state_eq_constraints=None,
Expand Down
14 changes: 14 additions & 0 deletions tests/test_clf_cbf.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,12 @@ def check_members(cls: mut.CompatibleClfCbf):
assert cls.y_squared_poly.shape == cls.y.shape
for y_squared_poly_i, y_i in zip(cls.y_squared_poly.flat, cls.y.flat):
assert y_squared_poly_i.EqualTo(sym.Polynomial(y_i**2))
assert cls.y_cross_poly.size == cls.y.size * (cls.y.size - 1) / 2
for y_cross in cls.y_cross_poly:
assert y_cross.TotalDegree() == 2
assert len(y_cross.monomial_to_coefficient_map()) == 1
for v in y_cross.indeterminates():
assert y_cross.Degree(v) == 1

assert dut.y.shape == (dut.num_cbf + 1,)
check_members(dut)
Expand Down Expand Up @@ -447,6 +453,7 @@ def test_search_compatible_lagrangians_w_clf_y_squared(self):
lambda_y=[mut.XYDegree(x=2, y=0) for _ in range(self.nu)],
xi_y=mut.XYDegree(x=2, y=0),
y=None,
y_cross=None,
rho_minus_V=mut.XYDegree(x=4, y=2),
h_plus_eps=[mut.XYDegree(x=4, y=2) for _ in range(dut.num_cbf)],
state_eq_constraints=None,
Expand Down Expand Up @@ -492,6 +499,7 @@ def test_add_compatibility_w_clf_y_squared(self):
)
xi_y_lagrangian = prog.NewFreePolynomial(dut.xy_set, deg=2)
y_lagrangian = None
y_cross_lagrangian = None
rho_minus_V_lagrangian, _ = prog.NewSosPolynomial(dut.xy_set, degree=2)
h_plus_eps_lagrangian = np.array(
[prog.NewSosPolynomial(dut.xy_set, degree=2)[0] for _ in range(dut.num_cbf)]
Expand All @@ -500,6 +508,7 @@ def test_add_compatibility_w_clf_y_squared(self):
lambda_y=lambda_y_lagrangian,
xi_y=xi_y_lagrangian,
y=y_lagrangian,
y_cross=y_cross_lagrangian,
rho_minus_V=rho_minus_V_lagrangian,
h_plus_eps=h_plus_eps_lagrangian,
state_eq_constraints=None,
Expand Down Expand Up @@ -566,6 +575,7 @@ def test_add_compatibility_w_vrep1(self):
],
xi_y=mut.XYDegree(x=2, y=2),
y=None,
y_cross=None,
rho_minus_V=mut.XYDegree(x=2, y=2),
h_plus_eps=[mut.XYDegree(x=2, y=4) for _ in range(h.size)],
state_eq_constraints=None,
Expand Down Expand Up @@ -641,6 +651,7 @@ def test_add_compatibility_w_vrep2(self):
],
xi_y=mut.XYDegree(x=2, y=2),
y=[mut.XYDegree(x=2, y=2) for _ in range(dut.y.size)],
y_cross=[mut.XYDegree(x=2, y=0) for _ in range(dut.y_cross_poly.size)],
rho_minus_V=mut.XYDegree(x=2, y=2),
h_plus_eps=[mut.XYDegree(x=2, y=4) for _ in range(h.size)],
state_eq_constraints=None,
Expand Down Expand Up @@ -670,6 +681,7 @@ def test_add_compatibility_w_vrep2(self):
)
- lagrangians.xi_y * (-xi.dot(dut.y_poly) - 1)
- lagrangians.y.dot(dut.y_poly)
- lagrangians.y_cross.dot(dut.y_cross_poly)
- lagrangians.rho_minus_V * (1 - V)
- lagrangians.h_plus_eps.dot(h + barrier_eps)
)
Expand Down Expand Up @@ -911,6 +923,7 @@ def search_lagrangians(
lambda_y=[mut.XYDegree(x=3, y=0)],
xi_y=mut.XYDegree(x=2, y=0),
y=None,
y_cross=None,
rho_minus_V=mut.XYDegree(x=2, y=0),
h_plus_eps=[mut.XYDegree(x=2, y=0)],
state_eq_constraints=None,
Expand Down Expand Up @@ -1265,6 +1278,7 @@ def search_lagrangians(self, check_result=False) -> Tuple[
lambda_y=[mut.XYDegree(x=2, y=0)],
xi_y=mut.XYDegree(x=2, y=0),
y=None,
y_cross=None,
rho_minus_V=mut.XYDegree(x=2, y=2),
h_plus_eps=[mut.XYDegree(x=2, y=2)],
state_eq_constraints=[mut.XYDegree(x=2, y=2)],
Expand Down