Skip to content

Commit 127a9ec

Browse files
Added an HeuristicPolicy with Control Lyapunov Controller (#74)
* Added an HeuristicPolicy with Control Lyapunov Controller for the navigation scenario. * Update vmas/scenarios/navigation.py Co-authored-by: Matteo Bettini <55539777+matteobettini@users.noreply.github.com> * Update vmas/scenarios/navigation.py Co-authored-by: Matteo Bettini <55539777+matteobettini@users.noreply.github.com> * Update vmas/scenarios/navigation.py Co-authored-by: Matteo Bettini <55539777+matteobettini@users.noreply.github.com> * Update vmas/scenarios/navigation.py Co-authored-by: Matteo Bettini <55539777+matteobettini@users.noreply.github.com> * Update vmas/scenarios/navigation.py Co-authored-by: Matteo Bettini <55539777+matteobettini@users.noreply.github.com> * Control parameters are defined in constructor * Improve readability of the Lyapunov function * Remove unused parameters * Update vmas/scenarios/navigation.py Co-authored-by: Matteo Bettini <55539777+matteobettini@users.noreply.github.com> * Formatted with black 23.1.0 * The control parameters are separated and can be defined individually. --------- Co-authored-by: Matteo Bettini <55539777+matteobettini@users.noreply.github.com>
1 parent aaec599 commit 127a9ec

1 file changed

Lines changed: 96 additions & 1 deletion

File tree

vmas/scenarios/navigation.py

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99

1010
from vmas import render_interactively
1111
from vmas.simulator.core import Agent, Landmark, World, Sphere, Entity
12+
from vmas.simulator.heuristic_policy import BaseHeuristicPolicy
1213
from vmas.simulator.scenario import BaseScenario
1314
from vmas.simulator.sensors import Lidar
14-
from vmas.simulator.utils import Color, ScenarioUtils
15+
from vmas.simulator.utils import Color, ScenarioUtils, X, Y
16+
1517

1618
if typing.TYPE_CHECKING:
1719
from vmas.simulator.rendering import Geom
@@ -283,6 +285,99 @@ def extra_render(self, env_index: int = 0) -> "List[Geom]":
283285
return geoms
284286

285287

288+
class HeuristicPolicy(BaseHeuristicPolicy):
289+
def __init__(self, clf_epsilon = 0.2, clf_slack = 100.0, *args, **kwargs):
290+
super().__init__(*args, **kwargs)
291+
self.clf_epsilon = clf_epsilon # Exponential CLF convergence rate
292+
self.clf_slack = clf_slack # weights on CLF-QP slack variable
293+
294+
def compute_action(self, observation: Tensor, u_range: Tensor) -> Tensor:
295+
"""
296+
QP inputs:
297+
These values need to computed apriri based on observation before passing into QP
298+
299+
V: Lyapunov function value
300+
lfV: Lie derivative of Lyapunov function
301+
lgV: Lie derivative of Lyapunov function
302+
CLF_slack: CLF constraint slack variable
303+
304+
QP outputs:
305+
u: action
306+
CLF_slack: CLF constraint slack variable, 0 if CLF constraint is satisfied
307+
"""
308+
# Install it with: pip install cvxpylayers
309+
import cvxpy as cp
310+
from cvxpylayers.torch import CvxpyLayer
311+
312+
self.n_env = observation.shape[0]
313+
self.device = observation.device
314+
agent_pos = observation[:, :2]
315+
agent_vel = observation[:, 2:4]
316+
goal_pos = (-1.0) * (observation[:, 4:6] - agent_pos)
317+
318+
# Pre-compute tensors for the CLF and CBF constraints,
319+
# Lyapunov Function from: https://arxiv.org/pdf/1903.03692.pdf
320+
321+
# Laypunov function
322+
V_value = (
323+
(agent_pos[:, X] - goal_pos[:, X]) ** 2
324+
+ 0.5 * (agent_pos[:, X] - goal_pos[:, X]) * agent_vel[:, X]
325+
+ agent_vel[:, X] ** 2
326+
+ (agent_pos[:, Y] - goal_pos[:, Y]) ** 2
327+
+ 0.5 * (agent_pos[:, Y] - goal_pos[:, Y]) * agent_vel[:, Y]
328+
+ agent_vel[:, Y] ** 2
329+
)
330+
331+
LfV_val = (2 * (agent_pos[:, X] - goal_pos[:, X]) + agent_vel[:, X]) * (
332+
agent_vel[:, X]
333+
) + (2 * (agent_pos[:, Y] - goal_pos[:, Y]) + agent_vel[:, Y]) * (
334+
agent_vel[:, Y]
335+
)
336+
LgV_vals = torch.stack(
337+
[
338+
0.5 * (agent_pos[:, X] - goal_pos[:, X]) + 2 * agent_vel[:, X],
339+
0.5 * (agent_pos[:, Y] - goal_pos[:, Y]) + 2 * agent_vel[:, Y],
340+
],
341+
dim=1,
342+
)
343+
# Define Quadratic Program (QP) based controller
344+
u = cp.Variable(2)
345+
V_param = cp.Parameter(1) # Lyapunov Function: V(x): x -> R, dim: (1,1)
346+
lfV_param = cp.Parameter(1)
347+
lgV_params = cp.Parameter(
348+
2
349+
) # Lie derivative of Lyapunov Function, dim: (1, action_dim)
350+
clf_slack = cp.Variable(1) # CLF constraint slack variable, dim: (1,1)
351+
352+
constraints = []
353+
354+
# QP Cost F = u^T @ u + clf_slack**2
355+
qp_objective = cp.Minimize(cp.sum_squares(u) + self.clf_slack * clf_slack**2)
356+
357+
# control bounds between u_range
358+
constraints += [u <= u_range]
359+
constraints += [u >= -u_range]
360+
# CLF constraint
361+
constraints += [
362+
lfV_param + lgV_params @ u + self.clf_epsilon * V_param + clf_slack <= 0
363+
]
364+
365+
QP_problem = cp.Problem(qp_objective, constraints)
366+
367+
# Initialize CVXPY layers
368+
QP_controller = CvxpyLayer(
369+
QP_problem, parameters=[V_param, lfV_param, lgV_params], variables=[u]
370+
)
371+
372+
# Solve QP
373+
CVXpylayer_parameters = [V_value.unsqueeze(1), LfV_val.unsqueeze(1), LgV_vals]
374+
action = QP_controller(*CVXpylayer_parameters, solver_args={"max_iters": 500})[
375+
0
376+
]
377+
378+
return action
379+
380+
286381
if __name__ == "__main__":
287382
render_interactively(
288383
__file__,

0 commit comments

Comments
 (0)