Control Examples

LQR Infinite Horizon Linear Dyanmics

The LQR algorithm can be run for the infinite horizon case using linear dynamics with the following. Note the state trajectory output can be used as a motion plan.

 1def linear_inf_horizon():
 2    import numpy as np
 3
 4    import gncpy.dynamics.basic as gdyn
 5    from gncpy.control import LQR
 6
 7    cur_time = 0
 8    dt = 0.1
 9    cur_state = np.array([0.5, 1, 0, 0]).reshape((-1, 1))
10    end_state = np.array([2, 4, 0, 0]).reshape((-1, 1))
11    time_horizon = float("inf")
12
13    # Create dynamics object
14    dynObj = gdyn.DoubleIntegrator()
15    dynObj.control_model = lambda _t, *_args: np.array(
16        [[0, 0], [0, 0], [1, 0], [0, 1]]
17    )
18    state_args = (dt,)
19
20    # Setup LQR Object
21    u_nom = np.zeros((2, 1))
22    Q = 10 * np.eye(len(dynObj.state_names))
23    R = 0.5 * np.eye(u_nom.size)
24    lqr = LQR(time_horizon=time_horizon)
25    # need to set dt here so the controller can generate a state trajectory
26    lqr.set_state_model(u_nom, dynObj=dynObj, dt=dt)
27    lqr.set_cost_model(Q, R)
28
29    u, cost, state_traj, ctrl_signal = lqr.calculate_control(
30        cur_time,
31        cur_state,
32        end_state=end_state,
33        end_state_tol=0.1,
34        check_inds=[0, 1],
35        state_args=state_args,
36        provide_details=True,
37    )
38
39    return state_traj, end_state

which gives this as output.

../_images/lqr_linear_inf_horizon.png

LQR Finite Horzon Linear Dynamics

The LQR algorithm can be run for the finite horizon case using non-linear dynamics with the following. Note the state trajectory output can be used as a motion plan.

 1def linear_finite_horizon():
 2    import numpy as np
 3
 4    import gncpy.dynamics.basic as gdyn
 5    from gncpy.control import LQR
 6
 7    cur_time = 0
 8    dt = 0.1
 9    cur_state = np.array([0.5, 1, 0, 0]).reshape((-1, 1))
10    end_state = np.array([2, 4, 0, 0]).reshape((-1, 1))
11    time_horizon = 3 * dt
12
13    # Create dynamics object
14    dynObj = gdyn.DoubleIntegrator()
15    dynObj.control_model = lambda _t, *_args: np.array(
16        [[0, 0], [0, 0], [1, 0], [0, 1]]
17    )
18    state_args = (dt,)
19    inv_state_args = (-dt, )
20
21    # Setup LQR Object
22    u_nom = np.zeros((2, 1))
23    Q = 200 * np.eye(len(dynObj.state_names))
24    R = 0.01 * np.eye(u_nom.size)
25    lqr = LQR(time_horizon=time_horizon)
26    # need to set dt here so the controller can generate a state trajectory
27    lqr.set_state_model(u_nom, dynObj=dynObj, dt=dt)
28    lqr.set_cost_model(Q, R)
29
30    u, cost, state_traj, ctrl_signal = lqr.calculate_control(
31        cur_time,
32        cur_state,
33        end_state=end_state,
34        state_args=state_args,
35        inv_state_args=inv_state_args,
36        provide_details=True,
37    )
38
39    return state_traj, end_state

which gives this as output.

../_images/lqr_linear_finite_horizon.png

LQR Finite Horzon Non-linear Dynamics

The LQR algorithm can be run for the finite horizon case using non-linear dynamics with the following. Note the state trajectory output can be used as a motion plan.

 1def nonlin_finite_hor():
 2    import numpy as np
 3
 4    import gncpy.dynamics.basic as gdyn
 5    from gncpy.control import LQR
 6
 7    d2r = np.pi / 180
 8
 9    cur_time = 0
10    dt = 0.1
11    cur_state = np.array([0.5, 0, 5, 45 * d2r]).reshape((-1, 1))
12    end_state = np.array([2, 0, 0, 0]).reshape((-1, 1))
13    time_horizon = 50 * dt
14
15    # Create dynamics object
16    dynObj = gdyn.CurvilinearMotion()
17
18    # Setup LQR Object
19    u_nom = np.zeros((2, 1))
20    Q = np.diag([200, 2000, 200, 2000])
21    R = 0.01 * np.eye(u_nom.size)
22    lqr = LQR(time_horizon=time_horizon)
23    # need to set dt here so the controller can generate a state trajectory
24    lqr.set_state_model(u_nom, dynObj=dynObj, dt=dt)
25    lqr.set_cost_model(Q, R)
26
27    u, cost, state_traj, ctrl_signal = lqr.calculate_control(
28        cur_time,
29        cur_state,
30        end_state=end_state,
31        provide_details=True,
32    )
33
34    return state_traj, end_state

which gives this as output.

../_images/lqr_nonlinear_finite_horizon.png

ELQR

The ELQR algorithm can be run for a finite horizon with non-linear dynamics and a non-quadratic cost with the following. Note the state trajectory output can be used as a motion plan. This version does not modify the quadratization process.

  1def basic():
  2    import numpy as np
  3
  4    import gncpy.plotting as gplot
  5    import gncpy.control as gctrl
  6    from gncpy.dynamics.basic import IRobotCreate
  7
  8    tt = 0  # starting time when calculating control
  9    dt = 1 / 6
 10    time_horizon = 150 * dt  # run for 150 timesteps
 11
 12    # define starting and ending state for control calculation
 13    end_state = np.array([0, 2.5, np.pi]).reshape((3, 1))
 14    start_state = np.array([0, -2.5, np.pi]).reshape((3, 1))
 15
 16    # define nominal control input
 17    u_nom = np.zeros((2, 1))
 18
 19    # define dynamics
 20    # IRobot Create has a dt that can be set here or it can be set by the control
 21    # algorithm
 22    dynObj = IRobotCreate(wheel_separation=0.258, radius=0.335 / 2.0)
 23
 24    # define some circular obstacles with center pos and radius (x, y, radius)
 25    obstacles = np.array(
 26        [
 27            [0, -1.35, 0.2],
 28            [1.0, -0.5, 0.2],
 29            [-0.95, -0.5, 0.2],
 30            [-0.2, 0.3, 0.2],
 31            [0.8, 0.7, 0.2],
 32            [1.1, 2.0, 0.2],
 33            [-1.2, 0.8, 0.2],
 34            [-1.1, 2.1, 0.2],
 35            [-0.1, 1.6, 0.2],
 36            [-1.1, -1.9, 0.2],
 37            [(10 + np.sqrt(2)) / 10, (-15 - np.sqrt(2)) / 10, 0.2],
 38        ]
 39    )
 40
 41    # define enviornment bounds for the robot
 42    bottom_left = np.array([-2, -3])
 43    top_right = np.array([2, 3])
 44
 45    # define Q and R weights for using standard cost function
 46    # Q = np.diag([50, 50, 0.4 * np.pi / 2])
 47    Q = 50 * np.eye(3)
 48    R = 0.6 * np.eye(u_nom.size)
 49
 50    # define non-quadratic term for cost function
 51    # has form: (tt, state, ctrl_input, end_state, is_initial, is_final, *args)
 52    obs_factor = 1
 53    scale_factor = 1
 54    cost_args = (obstacles, obs_factor, scale_factor, bottom_left, top_right)
 55
 56    def non_quadratic_cost(
 57        tt,
 58        state,
 59        ctrl_input,
 60        end_state,
 61        is_initial,
 62        is_final,
 63        _obstacles,
 64        _obs_factor,
 65        _scale_factor,
 66        _bottom_left,
 67        _top_right,
 68    ):
 69        cost = 0
 70        # cost for obstacles
 71        for obs in _obstacles:
 72            diff = state.ravel()[0:2] - obs[0:2]
 73            dist = np.sqrt(np.sum(diff * diff))
 74            # signed distance is negative if the robot is within the obstacle
 75            signed_dist = (dist - dynObj.radius) - obs[2]
 76            if signed_dist > 0:
 77                continue
 78            cost += _obs_factor * np.exp(-_scale_factor * signed_dist).item()
 79
 80        # add cost for going out of bounds
 81        for ii, b in enumerate(_bottom_left):
 82            dist = (state[ii] - b) - dynObj.radius
 83            cost += _obs_factor * np.exp(-_scale_factor * dist).item()
 84
 85        for ii, b in enumerate(_top_right):
 86            dist = (b - state[ii]) - dynObj.radius
 87            cost += _obs_factor * np.exp(-_scale_factor * dist).item()
 88
 89        return cost
 90
 91    # create control obect
 92    elqr = gctrl.ELQR(time_horizon=time_horizon)
 93    elqr.set_state_model(u_nom, dynObj=dynObj)
 94    elqr.dt = dt  # set here or within the dynamic object
 95    elqr.set_cost_model(Q=Q, R=R, non_quadratic_fun=non_quadratic_cost)
 96
 97    # create figure with obstacles to plot animation on
 98    fig = plt.figure()
 99    fig.add_subplot(1, 1, 1)
100    fig.axes[0].set_aspect("equal", adjustable="box")
101    fig.axes[0].set_xlim((bottom_left[0], top_right[0]))
102    fig.axes[0].set_ylim((bottom_left[1], top_right[1]))
103    fig.axes[0].scatter(
104        start_state[0], start_state[1], marker="o", color="g", zorder=1000
105    )
106    for obs in obstacles:
107        c = Circle(obs[:2], radius=obs[2], color="k", zorder=1000)
108        fig.axes[0].add_patch(c)
109    plt_opts = gplot.init_plotting_opts(f_hndl=fig)
110    gplot.set_title_label(fig, 0, plt_opts, ttl="ELQR")
111
112    # calculate control
113    u, cost, state_trajectory, control_signal, fig, frame_list = elqr.calculate_control(
114        tt,
115        start_state,
116        end_state,
117        cost_args=cost_args,
118        provide_details=True,
119        show_animation=True,
120        save_animation=True,
121        plt_inds=[0, 1],
122        fig=fig,
123    )
124
125    return frame_list

which gives this as output.

../_images/elqr_basic.gif

ELQR Modified Quadratization

The ELQR algorithm can be run for a finite horizon with non-linear dynamics and a non-quadratic cost with the following. Note the state trajectory output can be used as a motion plan. This version does modifies the quadratization process.

  1def modify_quadratize():
  2    import numpy as np
  3
  4    import gncpy.plotting as gplot
  5    import gncpy.control as gctrl
  6    from gncpy.dynamics.basic import IRobotCreate
  7
  8    tt = 0  # starting time when calculating control
  9    dt = 1 / 6
 10    time_horizon = 150 * dt  # run for 150 timesteps
 11
 12    # define starting and ending state for control calculation
 13    end_state = np.array([0, 2.5, np.pi]).reshape((3, 1))
 14    start_state = np.array([0, -2.5, np.pi]).reshape((3, 1))
 15
 16    # define nominal control input
 17    u_nom = np.zeros((2, 1))
 18
 19    # define dynamics
 20    # IRobot Create has a dt that can be set here or it can be set by the control
 21    # algorithm
 22    dynObj = IRobotCreate(wheel_separation=0.258, radius=0.335 / 2.0)
 23
 24    # define some circular obstacles with center pos and radius (x, y, radius)
 25    obstacles = np.array(
 26        [
 27            [0, -1.35, 0.2],
 28            [1.0, -0.5, 0.2],
 29            [-0.95, -0.5, 0.2],
 30            [-0.2, 0.3, 0.2],
 31            [0.8, 0.7, 0.2],
 32            [1.1, 2.0, 0.2],
 33            [-1.2, 0.8, 0.2],
 34            [-1.1, 2.1, 0.2],
 35            [-0.1, 1.6, 0.2],
 36            [-1.1, -1.9, 0.2],
 37            [(10 + np.sqrt(2)) / 10, (-15 - np.sqrt(2)) / 10, 0.2],
 38        ]
 39    )
 40
 41    # define enviornment bounds for the robot
 42    bottom_left = np.array([-2, -3])
 43    top_right = np.array([2, 3])
 44
 45    # define Q and R weights for using standard cost function
 46    Q = 50 * np.eye(len(dynObj.state_names))
 47    R = 0.6 * np.eye(u_nom.size)
 48
 49    # define non-quadratic term for cost function
 50    # has form: (tt, state, ctrl_input, end_state, is_initial, is_final, *args)
 51    obs_factor = 1
 52    scale_factor = 1
 53    cost_args = (obstacles, obs_factor, scale_factor, bottom_left, top_right)
 54
 55    def non_quadratic_cost(
 56        tt,
 57        state,
 58        ctrl_input,
 59        end_state,
 60        is_initial,
 61        is_final,
 62        _obstacles,
 63        _obs_factor,
 64        _scale_factor,
 65        _bottom_left,
 66        _top_right,
 67    ):
 68        cost = 0
 69        # cost for obstacles
 70        for obs in _obstacles:
 71            diff = state.ravel()[0:2] - obs[0:2]
 72            dist = np.sqrt(np.sum(diff * diff))
 73            # signed distance is negative if the robot is within the obstacle
 74            signed_dist = (dist - dynObj.radius) - obs[2]
 75            if signed_dist > 0:
 76                continue
 77            cost += _obs_factor * np.exp(-_scale_factor * signed_dist).item()
 78
 79        # add cost for going out of bounds
 80        for ii, b in enumerate(_bottom_left):
 81            dist = (state[ii] - b) - dynObj.radius
 82            cost += _obs_factor * np.exp(-_scale_factor * dist).item()
 83
 84        for ii, b in enumerate(_top_right):
 85            dist = (b - state[ii]) - dynObj.radius
 86            cost += _obs_factor * np.exp(-_scale_factor * dist).item()
 87
 88        return cost
 89
 90    # define modifications for quadratizing the cost function
 91    def quad_modifier(itr, tt, P, Q, R, q, r):
 92        rot_cost = 0.4
 93        # only modify if within the first 2 iterations
 94        if itr < 2:
 95            Q[-1, -1] = rot_cost
 96            q[-1] = -rot_cost * np.pi / 2
 97
 98        return P, Q, R, q, r
 99
100    # create control obect
101    elqr = gctrl.ELQR(time_horizon=time_horizon)
102    elqr.set_state_model(u_nom, dynObj=dynObj)
103    elqr.dt = dt  # set here or within the dynamic object
104    elqr.set_cost_model(
105        Q=Q, R=R, non_quadratic_fun=non_quadratic_cost, quad_modifier=quad_modifier
106    )
107
108    fig = plt.figure()
109    fig.add_subplot(1, 1, 1)
110    fig.axes[0].set_aspect("equal", adjustable="box")
111    fig.axes[0].set_xlim((bottom_left[0], top_right[0]))
112    fig.axes[0].set_ylim((bottom_left[1], top_right[1]))
113    fig.axes[0].scatter(
114        start_state[0], start_state[1], marker="o", color="g", zorder=1000
115    )
116    for obs in obstacles:
117        c = Circle(obs[:2], radius=obs[2], color="k", zorder=1000)
118        fig.axes[0].add_patch(c)
119    plt_opts = gplot.init_plotting_opts(f_hndl=fig)
120    gplot.set_title_label(fig, 0, plt_opts, ttl="ELQR with Modified Quadratize Cost")
121
122    # calculate control
123    u, cost, state_trajectory, control_signal, fig, frame_list = elqr.calculate_control(
124        tt,
125        start_state,
126        end_state,
127        cost_args=cost_args,
128        provide_details=True,
129        show_animation=True,
130        save_animation=True,
131        plt_inds=[0, 1],
132        fig=fig,
133    )
134
135    return frame_list

which gives this as output.

../_images/elqr_modify_quadratize.gif

ELQR Linear Dynamics

The ELQR algorithm can be run for a finite horizon with linear dynamics and a non-quadratic cost with the following. Note the state trajectory output can be used as a motion plan. This version uses dynamics with a constraint on the states in addition to the obstacles in the environment.

  1def linear():
  2    import numpy as np
  3
  4    import gncpy.plotting as gplot
  5    import gncpy.control as gctrl
  6    from gncpy.dynamics.basic import DoubleIntegrator
  7
  8    tt = 0  # starting time when calculating control
  9    dt = 1 / 6
 10    time_horizon = 20 * dt  # run for 150 timesteps
 11
 12    # define starting and ending state for control calculation
 13    end_state = np.array([0, 2.5, 0, 0]).reshape((-1, 1))
 14    start_state = np.array([0, -2.5, 0, 0]).reshape((-1, 1))
 15
 16    # define nominal control input
 17    u_nom = np.zeros((2, 1))
 18
 19    # define dynamics
 20    dynObj = DoubleIntegrator()
 21
 22    # set control model
 23    dynObj.control_model = lambda t, *args: np.array(
 24        [[0, 0], [0, 0], [1, 0], [0, 1]], dtype=float
 25    )
 26
 27    # set a state constraint on the velocities
 28    dynObj.state_constraint = lambda t, x: np.vstack(
 29        (
 30            x[:2],
 31            np.max(
 32                (np.min((x[2:], 2 * np.ones((2, 1))), axis=0), -2 * np.ones((2, 1))),
 33                axis=0,
 34            ),
 35        )
 36    )
 37
 38    # define some circular obstacles with center pos and radius (x, y, radius)
 39    obstacles = np.array(
 40        [
 41            [0, -1.35, 0.2],
 42            [1.0, -0.5, 0.2],
 43            [-0.95, -0.5, 0.2],
 44            [-0.2, 0.3, 0.2],
 45            [0.8, 0.7, 0.2],
 46            [1.1, 2.0, 0.2],
 47            [-1.2, 0.8, 0.2],
 48            [-1.1, 2.1, 0.2],
 49            [-0.1, 1.6, 0.2],
 50            [-1.1, -1.9, 0.2],
 51            [(10 + np.sqrt(2)) / 10, (-15 - np.sqrt(2)) / 10, 0.2],
 52        ]
 53    )
 54
 55    # define enviornment bounds for the robot
 56    bottom_left = np.array([-2, -3])
 57    top_right = np.array([2, 3])
 58
 59    # define Q and R weights for using standard cost function
 60    Q = 10 * np.eye(len(dynObj.state_names))
 61    R = 0.6 * np.eye(u_nom.size)
 62
 63    # define non-quadratic term for cost function
 64    # has form: (tt, state, ctrl_input, end_state, is_initial, is_final, *args)
 65    obs_factor = 1
 66    scale_factor = 1
 67    cost_args = (obstacles, obs_factor, scale_factor, bottom_left, top_right)
 68
 69    def non_quadratic_cost(
 70        tt,
 71        state,
 72        ctrl_input,
 73        end_state,
 74        is_initial,
 75        is_final,
 76        _obstacles,
 77        _obs_factor,
 78        _scale_factor,
 79        _bottom_left,
 80        _top_right,
 81    ):
 82        cost = 0
 83        radius = 0.335 / 2.0
 84        # cost for obstacles
 85        for obs in _obstacles:
 86            diff = state.ravel()[0:2] - obs[0:2]
 87            dist = np.sqrt(np.sum(diff * diff))
 88            # signed distance is negative if the robot is within the obstacle
 89            signed_dist = (dist - radius) - obs[2]
 90            if signed_dist > 0:
 91                continue
 92            cost += _obs_factor * np.exp(-_scale_factor * signed_dist).item()
 93
 94        # add cost for going out of bounds
 95        for ii, b in enumerate(_bottom_left):
 96            dist = (state[ii] - b) - radius
 97            cost += _obs_factor * np.exp(-_scale_factor * dist).item()
 98
 99        for ii, b in enumerate(_top_right):
100            dist = (b - state[ii]) - radius
101            cost += _obs_factor * np.exp(-_scale_factor * dist).item()
102
103        return cost
104
105    # create control obect
106    elqr = gctrl.ELQR(time_horizon=time_horizon)
107    elqr.set_state_model(u_nom, dynObj=dynObj)
108    elqr.dt = dt  # set here or within the dynamic object
109    elqr.set_cost_model(Q=Q, R=R, non_quadratic_fun=non_quadratic_cost)
110
111    fig = plt.figure()
112    fig.add_subplot(1, 1, 1)
113    fig.axes[0].set_aspect("equal", adjustable="box")
114    fig.axes[0].set_xlim((bottom_left[0], top_right[0]))
115    fig.axes[0].set_ylim((bottom_left[1], top_right[1]))
116    fig.axes[0].scatter(
117        start_state[0], start_state[1], marker="o", color="g", zorder=1000
118    )
119    for obs in obstacles:
120        c = Circle(obs[:2], radius=obs[2], color="k", zorder=1000)
121        fig.axes[0].add_patch(c)
122    plt_opts = gplot.init_plotting_opts(f_hndl=fig)
123    gplot.set_title_label(fig, 0, plt_opts, ttl="ELQR with Linear Dynamics Cost")
124
125    # calculate control
126    u, cost, state_trajectory, control_signal, fig, frame_list = elqr.calculate_control(
127        tt,
128        start_state,
129        end_state,
130        state_args=(dt,),
131        cost_args=cost_args,
132        inv_state_args=(-dt,),
133        provide_details=True,
134        show_animation=True,
135        save_animation=True,
136        plt_inds=[0, 1],
137        fig=fig,
138    )
139
140    return frame_list

which gives this as output.

../_images/elqr_linear.gif