Add: TwoWheeledTrack Env

This commit is contained in:
Shunichi09 2020-05-02 21:08:25 +09:00
parent 1ed489cfc4
commit 2a44536a51
23 changed files with 734 additions and 43 deletions

View File

@ -1 +1,44 @@
import numpy as np import numpy as np
def rotate_pos(pos, angle):
""" Transformation the coordinate in the angle
Args:
pos (numpy.ndarray): local state, shape(data_size, 2)
angle (float): rotate angle, in radians
Returns:
rotated_pos (numpy.ndarray): shape(data_size, 2)
"""
rot_mat = np.array([[np.cos(angle), -np.sin(angle)],
[np.sin(angle), np.cos(angle)]])
return np.dot(pos, rot_mat.T)
def fit_angle_in_range(angles, min_angle=-np.pi, max_angle=np.pi):
""" Check angle range and correct the range
Args:
angle (numpy.ndarray): in radians
min_angle (float): maximum of range in radians, default -pi
max_angle (float): minimum of range in radians, default pi
Returns:
fitted_angle (numpy.ndarray): range angle in radians
"""
if max_angle < min_angle:
raise ValueError("max angle must be greater than min angle")
if (max_angle - min_angle) < 2.0 * np.pi:
raise ValueError("difference between max_angle \
and min_angle must be greater than 2.0 * pi")
output = np.array(angles)
output_shape = output.shape
output = output.flatten()
output -= min_angle
output %= 2 * np.pi
output += 2 * np.pi
output %= 2 * np.pi
output += min_angle
output = np.minimum(max_angle, np.maximum(min_angle, output))
return output.reshape(output_shape)

View File

@ -3,6 +3,7 @@ import numpy as np
class CartPoleConfigModule(): class CartPoleConfigModule():
# parameters # parameters
ENV_NAME = "CartPole-v0" ENV_NAME = "CartPole-v0"
PLANNER_TYPE = "Const"
TYPE = "Nonlinear" TYPE = "Nonlinear"
TASK_HORIZON = 500 TASK_HORIZON = 500
PRED_LEN = 50 PRED_LEN = 50
@ -10,9 +11,9 @@ class CartPoleConfigModule():
INPUT_SIZE = 1 INPUT_SIZE = 1
DT = 0.02 DT = 0.02
# cost parameters # cost parameters
R = np.diag([1.]) # 0.01 is worked for MPPI and CEM and MPPIWilliams R = np.diag([0.01]) # 0.01 is worked for MPPI and CEM and MPPIWilliams
# 1. is worked for iLQR # 1. is worked for iLQR
Terminal_Weight = 1. TERMINAL_WEIGHT = 1.
Q = None Q = None
Sf = None Sf = None
# bounds # bounds
@ -23,6 +24,7 @@ class CartPoleConfigModule():
MC = 1. MC = 1.
L = 0.5 L = 0.5
G = 9.81 G = 9.81
CART_SIZE = (0.15, 0.1)
def __init__(self): def __init__(self):
""" """
@ -76,6 +78,7 @@ class CartPoleConfigModule():
@staticmethod @staticmethod
def input_cost_fn(u): def input_cost_fn(u):
""" input cost functions """ input cost functions
Args: Args:
u (numpy.ndarray): input, shape(pred_len, input_size) u (numpy.ndarray): input, shape(pred_len, input_size)
or shape(pop_size, pred_len, input_size) or shape(pop_size, pred_len, input_size)
@ -88,6 +91,7 @@ class CartPoleConfigModule():
@staticmethod @staticmethod
def state_cost_fn(x, g_x): def state_cost_fn(x, g_x):
""" state cost function """ state cost function
Args: Args:
x (numpy.ndarray): state, shape(pred_len, state_size) x (numpy.ndarray): state, shape(pred_len, state_size)
or shape(pop_size, pred_len, state_size) or shape(pop_size, pred_len, state_size)
@ -118,6 +122,7 @@ class CartPoleConfigModule():
@staticmethod @staticmethod
def terminal_state_cost_fn(terminal_x, terminal_g_x): def terminal_state_cost_fn(terminal_x, terminal_g_x):
""" """
Args: Args:
terminal_x (numpy.ndarray): terminal state, terminal_x (numpy.ndarray): terminal state,
shape(state_size, ) or shape(pop_size, state_size) shape(state_size, ) or shape(pop_size, state_size)
@ -133,13 +138,13 @@ class CartPoleConfigModule():
+ 12. * ((np.cos(terminal_x[:, 2]) + 1.)**2) \ + 12. * ((np.cos(terminal_x[:, 2]) + 1.)**2) \
+ 0.1 * (terminal_x[:, 1]**2) \ + 0.1 * (terminal_x[:, 1]**2) \
+ 0.1 * (terminal_x[:, 3]**2))[:, np.newaxis] \ + 0.1 * (terminal_x[:, 3]**2))[:, np.newaxis] \
* CartPoleConfigModule.Terminal_Weight * CartPoleConfigModule.TERMINAL_WEIGHT
return (6. * (terminal_x[0]**2) \ return (6. * (terminal_x[0]**2) \
+ 12. * ((np.cos(terminal_x[2]) + 1.)**2) \ + 12. * ((np.cos(terminal_x[2]) + 1.)**2) \
+ 0.1 * (terminal_x[1]**2) \ + 0.1 * (terminal_x[1]**2) \
+ 0.1 * (terminal_x[3]**2)) \ + 0.1 * (terminal_x[3]**2)) \
* CartPoleConfigModule.Terminal_Weight * CartPoleConfigModule.TERMINAL_WEIGHT
@staticmethod @staticmethod
def gradient_cost_fn_with_state(x, g_x, terminal=False): def gradient_cost_fn_with_state(x, g_x, terminal=False):
@ -168,7 +173,7 @@ class CartPoleConfigModule():
cost_dx3 = 0.2 * x[3] cost_dx3 = 0.2 * x[3]
cost_dx = np.array([[cost_dx0, cost_dx1, cost_dx2, cost_dx3]]) cost_dx = np.array([[cost_dx0, cost_dx1, cost_dx2, cost_dx3]])
return cost_dx * CartPoleConfigModule.Terminal_Weight return cost_dx * CartPoleConfigModule.TERMINAL_WEIGHT
@staticmethod @staticmethod
def gradient_cost_fn_with_input(x, u): def gradient_cost_fn_with_input(x, u):
@ -177,7 +182,6 @@ class CartPoleConfigModule():
Args: Args:
x (numpy.ndarray): state, shape(pred_len, state_size) x (numpy.ndarray): state, shape(pred_len, state_size)
u (numpy.ndarray): goal state, shape(pred_len, input_size) u (numpy.ndarray): goal state, shape(pred_len, input_size)
Returns: Returns:
l_u (numpy.ndarray): gradient of cost, shape(pred_len, input_size) l_u (numpy.ndarray): gradient of cost, shape(pred_len, input_size)
""" """
@ -190,7 +194,6 @@ class CartPoleConfigModule():
Args: Args:
x (numpy.ndarray): state, shape(pred_len, state_size) x (numpy.ndarray): state, shape(pred_len, state_size)
g_x (numpy.ndarray): goal state, shape(pred_len, state_size) g_x (numpy.ndarray): goal state, shape(pred_len, state_size)
Returns: Returns:
l_xx (numpy.ndarray): gradient of cost, l_xx (numpy.ndarray): gradient of cost,
shape(pred_len, state_size, state_size) or shape(pred_len, state_size, state_size) or
@ -220,7 +223,7 @@ class CartPoleConfigModule():
* -np.cos(x[2]) * -np.cos(x[2])
hessian[3, 3] = 0.2 hessian[3, 3] = 0.2
return hessian[np.newaxis, :, :] * CartPoleConfigModule.Terminal_Weight return hessian[np.newaxis, :, :] * CartPoleConfigModule.TERMINAL_WEIGHT
@staticmethod @staticmethod
def hessian_cost_fn_with_input(x, u): def hessian_cost_fn_with_input(x, u):
@ -229,7 +232,6 @@ class CartPoleConfigModule():
Args: Args:
x (numpy.ndarray): state, shape(pred_len, state_size) x (numpy.ndarray): state, shape(pred_len, state_size)
u (numpy.ndarray): goal state, shape(pred_len, input_size) u (numpy.ndarray): goal state, shape(pred_len, input_size)
Returns: Returns:
l_uu (numpy.ndarray): gradient of cost, l_uu (numpy.ndarray): gradient of cost,
shape(pred_len, input_size, input_size) shape(pred_len, input_size, input_size)
@ -245,7 +247,6 @@ class CartPoleConfigModule():
Args: Args:
x (numpy.ndarray): state, shape(pred_len, state_size) x (numpy.ndarray): state, shape(pred_len, state_size)
u (numpy.ndarray): goal state, shape(pred_len, input_size) u (numpy.ndarray): goal state, shape(pred_len, input_size)
Returns: Returns:
l_ux (numpy.ndarray): gradient of cost , l_ux (numpy.ndarray): gradient of cost ,
shape(pred_len, input_size, state_size) shape(pred_len, input_size, state_size)

View File

@ -9,7 +9,7 @@ def make_config(args):
""" """
if args.env == "FirstOrderLag": if args.env == "FirstOrderLag":
return FirstOrderLagConfigModule() return FirstOrderLagConfigModule()
elif args.env == "TwoWheeledConst" or args.env == "TwoWheeled": elif args.env == "TwoWheeledConst" or args.env == "TwoWheeledTrack":
return TwoWheeledConfigModule() return TwoWheeledConfigModule()
elif args.env == "CartPole": elif args.env == "CartPole":
return CartPoleConfigModule() return CartPoleConfigModule()

View File

@ -1,21 +1,37 @@
import numpy as np import numpy as np
from matplotlib.axes import Axes
from ..plotters.plot_objs import square_with_angle, square
from ..common.utils import fit_angle_in_range
class TwoWheeledConfigModule(): class TwoWheeledConfigModule():
# parameters # parameters
ENV_NAME = "TwoWheeled-v0" ENV_NAME = "TwoWheeled-v0"
TYPE = "Nonlinear" TYPE = "Nonlinear"
N_AHEAD = 1
TASK_HORIZON = 1000 TASK_HORIZON = 1000
PRED_LEN = 20 PRED_LEN = 20
STATE_SIZE = 3 STATE_SIZE = 3
INPUT_SIZE = 2 INPUT_SIZE = 2
DT = 0.01 DT = 0.01
# cost parameters # cost parameters
# for Const goal
"""
R = np.diag([0.1, 0.1]) R = np.diag([0.1, 0.1])
Q = np.diag([1., 1., 0.01]) Q = np.diag([1., 1., 0.01])
Sf = np.diag([5., 5., 1.]) Sf = np.diag([5., 5., 1.])
"""
# for track goal
R = np.diag([0.01, 0.01])
Q = np.diag([2.5, 2.5, 0.01])
Sf = np.diag([2.5, 2.5, 0.01])
# bounds # bounds
INPUT_LOWER_BOUND = np.array([-1.5, 3.14]) INPUT_LOWER_BOUND = np.array([-1.5, -3.14])
INPUT_UPPER_BOUND = np.array([1.5, 3.14]) INPUT_UPPER_BOUND = np.array([1.5, 3.14])
# parameters
CAR_SIZE = 0.2
WHEELE_SIZE = (0.075, 0.015)
def __init__(self): def __init__(self):
""" """
@ -78,6 +94,27 @@ class TwoWheeledConfigModule():
""" """
return (u**2) * np.diag(TwoWheeledConfigModule.R) return (u**2) * np.diag(TwoWheeledConfigModule.R)
@staticmethod
def fit_diff_in_range(diff_x):
""" fit difference state in range(angle)
Args:
diff_x (numpy.ndarray):
shape(pop_size, pred_len, state_size) or
shape(pred_len, state_size) or
shape(state_size, )
Returns:
fitted_diff_x (numpy.ndarray): same shape as diff_x
"""
if len(diff_x.shape) == 3:
diff_x[:, :, -1] = fit_angle_in_range(diff_x[:, :, -1])
elif len(diff_x.shape) == 2:
diff_x[:, -1] = fit_angle_in_range(diff_x[:, -1])
elif len(diff_x.shape) == 1:
diff_x[-1] = fit_angle_in_range(diff_x[-1])
return diff_x
@staticmethod @staticmethod
def state_cost_fn(x, g_x): def state_cost_fn(x, g_x):
""" state cost function """ state cost function
@ -90,7 +127,8 @@ class TwoWheeledConfigModule():
cost (numpy.ndarray): cost of state, shape(pred_len, state_size) or cost (numpy.ndarray): cost of state, shape(pred_len, state_size) or
shape(pop_size, pred_len, state_size) shape(pop_size, pred_len, state_size)
""" """
return ((x - g_x)**2) * np.diag(TwoWheeledConfigModule.Q) diff = TwoWheeledConfigModule.fit_diff_in_range(x - g_x)
return ((diff)**2) * np.diag(TwoWheeledConfigModule.Q)
@staticmethod @staticmethod
def terminal_state_cost_fn(terminal_x, terminal_g_x): def terminal_state_cost_fn(terminal_x, terminal_g_x):
@ -104,8 +142,10 @@ class TwoWheeledConfigModule():
cost (numpy.ndarray): cost of state, shape(pred_len, ) or cost (numpy.ndarray): cost of state, shape(pred_len, ) or
shape(pop_size, pred_len) shape(pop_size, pred_len)
""" """
return ((terminal_x - terminal_g_x)**2) \ terminal_diff = TwoWheeledConfigModule.fit_diff_in_range(terminal_x \
* np.diag(TwoWheeledConfigModule.Sf) - terminal_g_x)
return ((terminal_diff)**2) * np.diag(TwoWheeledConfigModule.Sf)
@staticmethod @staticmethod
def gradient_cost_fn_with_state(x, g_x, terminal=False): def gradient_cost_fn_with_state(x, g_x, terminal=False):
@ -119,10 +159,12 @@ class TwoWheeledConfigModule():
l_x (numpy.ndarray): gradient of cost, shape(pred_len, state_size) l_x (numpy.ndarray): gradient of cost, shape(pred_len, state_size)
or shape(1, state_size) or shape(1, state_size)
""" """
if not terminal: diff = TwoWheeledConfigModule.fit_diff_in_range(x - g_x)
return 2. * (x - g_x) * np.diag(TwoWheeledConfigModule.Q)
return (2. * (x - g_x) \ if not terminal:
return 2. * (diff) * np.diag(TwoWheeledConfigModule.Q)
return (2. * (diff) \
* np.diag(TwoWheeledConfigModule.Sf))[np.newaxis, :] * np.diag(TwoWheeledConfigModule.Sf))[np.newaxis, :]
@staticmethod @staticmethod

View File

@ -21,4 +21,6 @@ def make_controller(args, config, model):
elif args.controller_type == "iLQR": elif args.controller_type == "iLQR":
return iLQR(config, model) return iLQR(config, model)
elif args.controller_type == "DDP": elif args.controller_type == "DDP":
return DDP(config, model) return DDP(config, model)
raise ValueError("No controller: {}".format(args.controller_type))

View File

@ -1,7 +1,8 @@
from logging import getLogger from logging import getLogger
import numpy as np import numpy as np
from cvxopt import matrix, solvers from scipy.optimize import minimize
from scipy.optimize import LinearConstraint
from .controller import Controller from .controller import Controller
from ..envs.cost import calc_cost from ..envs.cost import calc_cost
@ -61,6 +62,7 @@ class LinearMPC(Controller):
self.F = None self.F = None
self.f = None self.f = None
self.setup() self.setup()
self.prev_sol = np.zeros(self.input_size*self.pred_len)
# history # history
self.history_u = [np.zeros(self.input_size)] self.history_u = [np.zeros(self.input_size)]
@ -183,6 +185,22 @@ class LinearMPC(Controller):
ub = np.array(b).flatten() ub = np.array(b).flatten()
# using cvxopt
def optimized_func(dt_us):
return (np.dot(dt_us, np.dot(H, dt_us.reshape(-1, 1))) \
- np.dot(G.T, dt_us.reshape(-1, 1)))[0]
# constraint
lb = np.array([-np.inf for _ in range(len(ub))]) # one side cons
cons = LinearConstraint(A, lb, ub)
# solve
opt_sol = minimize(optimized_func, self.prev_sol.flatten(),\
constraints=[cons])
opt_dt_us = opt_sol.x
""" using cvxopt ver,
if you want to solve more quick please use cvxopt instead of scipy
# make cvxpy problem formulation # make cvxpy problem formulation
P = 2*matrix(H) P = 2*matrix(H)
q = matrix(-1 * G) q = matrix(-1 * G)
@ -190,12 +208,15 @@ class LinearMPC(Controller):
b = matrix(ub) b = matrix(ub)
# solve the problem # solve the problem
opt_result = solvers.qp(P, q, G=A, h=b) opt_sol = solvers.qp(P, q, G=A, h=b)
opt_dt_us = np.array(list(opt_result['x'])) opt_dt_us = np.array(list(opt_sol['x']))
"""
# to dt form # to dt form
opt_dt_u_seq = np.cumsum(opt_dt_us.reshape(self.pred_len,\ opt_dt_u_seq = np.cumsum(opt_dt_us.reshape(self.pred_len,\
self.input_size), self.input_size),
axis=0) axis=0)
self.prev_sol = opt_dt_u_seq.copy()
opt_u_seq = opt_dt_u_seq + self.history_u[-1] opt_u_seq = opt_dt_u_seq + self.history_u[-1]

View File

@ -1,6 +1,8 @@
import numpy as np import numpy as np
from matplotlib.axes import Axes
from .env import Env from .env import Env
from ..plotters.plot_objs import square
class CartPoleEnv(Env): class CartPoleEnv(Env):
""" Cartpole Environment """ Cartpole Environment
@ -24,6 +26,7 @@ class CartPoleEnv(Env):
"mc": 1., "mc": 1.,
"l": 0.5, "l": 0.5,
"g": 9.81, "g": 9.81,
"cart_size": (0.15, 0.1),
} }
super(CartPoleEnv, self).__init__(self.config) super(CartPoleEnv, self).__init__(self.config)
@ -112,4 +115,64 @@ class CartPoleEnv(Env):
return next_x.flatten(), costs, \ return next_x.flatten(), costs, \
self.step_count > self.config["max_step"], \ self.step_count > self.config["max_step"], \
{"goal_state" : self.g_x} {"goal_state" : self.g_x}
def plot_func(self, to_plot, i=None, history_x=None, history_g_x=None):
""" plot cartpole object function
Args:
to_plot (axis or imgs): plotted objects
i (int): frame count
history_x (numpy.ndarray): history of state, shape(iters, state)
history_g_x (numpy.ndarray): history of goal state,
shape(iters, state)
Returns:
None or imgs : imgs order is ["cart_img", "pole_img"]
"""
if isinstance(to_plot, Axes):
imgs = {} # create new imgs
imgs["cart"] = to_plot.plot([], [], c="k")[0]
imgs["pole"] = to_plot.plot([], [], c="k", linewidth=5)[0]
imgs["center"] = to_plot.plot([], [], marker="o", c="k",\
markersize=10)[0]
# centerline
to_plot.plot(np.linspace(-1., 1., num=50), np.zeros(50),\
c="k", linestyle="dashed")
# set axis
to_plot.set_xlim([-1., 1.])
to_plot.set_ylim([-0.55, 1.5])
return imgs
# set imgs
cart_x, cart_y, pole_x, pole_y = \
self._plot_cartpole(history_x[i])
to_plot["cart"].set_data(cart_x, cart_y)
to_plot["pole"].set_data(pole_x, pole_y)
to_plot["center"].set_data(history_x[i][0], 0.)
def _plot_cartpole(self, curr_x):
""" plot cartpole fucntions
Args:
curr_x (numpy.ndarray): current catpole state
Returns:
cart_x (numpy.ndarray): x data of cart
cart_y (numpy.ndarray): y data of cart
pole_x (numpy.ndarray): x data of pole
pole_y (numpy.ndarray): y data of pole
"""
# cart
cart_x, cart_y = square(curr_x[0], 0.,\
self.config["cart_size"], 0.)
# pole
pole_x = np.array([curr_x[0], curr_x[0] + self.config["l"] \
* np.cos(curr_x[2]-np.pi/2)])
pole_y = np.array([0., self.config["l"] \
* np.sin(curr_x[2]-np.pi/2)])
return cart_x, cart_y, pole_x, pole_y

View File

@ -1,8 +1,9 @@
import numpy as np import numpy as np
class Env(): class Env():
""" """ Environments class
Attributes: Attributes:
curr_x (numpy.ndarray): current state curr_x (numpy.ndarray): current state
history_x (list[numpy.ndarray]): historty of state, shape(step_count*state_size) history_x (list[numpy.ndarray]): historty of state, shape(step_count*state_size)
step_count (int): step count step_count (int): step count
@ -48,7 +49,7 @@ class Env():
""" """
raise NotImplementedError("Implement step function") raise NotImplementedError("Implement step function")
def __str__(self): def __repr__(self):
""" """
""" """
return self.config return self.config

View File

@ -112,4 +112,9 @@ class FirstOrderLagEnv(Env):
return next_x.flatten(), cost, \ return next_x.flatten(), cost, \
self.step_count > self.config["max_step"], \ self.step_count > self.config["max_step"], \
{"goal_state" : self.g_x} {"goal_state" : self.g_x}
def plot_func(self, to_plot, i=None, history_x=None, history_g_x=None):
"""
"""
raise ValueError("FirstOrderLag does not have animation")

View File

@ -1,5 +1,6 @@
from .first_order_lag import FirstOrderLagEnv from .first_order_lag import FirstOrderLagEnv
from .two_wheeled import TwoWheeledConstEnv from .two_wheeled import TwoWheeledConstEnv
from .two_wheeled import TwoWheeledTrackEnv
from .cartpole import CartPoleEnv from .cartpole import CartPoleEnv
def make_env(args): def make_env(args):
@ -8,6 +9,8 @@ def make_env(args):
return FirstOrderLagEnv() return FirstOrderLagEnv()
elif args.env == "TwoWheeledConst": elif args.env == "TwoWheeledConst":
return TwoWheeledConstEnv() return TwoWheeledConstEnv()
elif args.env == "TwoWheeledTrack":
return TwoWheeledTrackEnv()
elif args.env == "CartPole": elif args.env == "CartPole":
return CartPoleEnv() return CartPoleEnv()

View File

@ -1,6 +1,9 @@
import numpy as np import numpy as np
from matplotlib.axes import Axes
import matplotlib.pyplot as plt
from .env import Env from .env import Env
from ..plotters.plot_objs import circle_with_angle, square, circle
def step_two_wheeled_env(curr_x, u, dt, method="Oylar"): def step_two_wheeled_env(curr_x, u, dt, method="Oylar"):
""" step two wheeled enviroment """ step two wheeled enviroment
@ -34,9 +37,11 @@ class TwoWheeledConstEnv(Env):
self.config = {"state_size" : 3,\ self.config = {"state_size" : 3,\
"input_size" : 2,\ "input_size" : 2,\
"dt" : 0.01,\ "dt" : 0.01,\
"max_step" : 1000,\ "max_step" : 500,\
"input_lower_bound": [-1.5, -3.14],\ "input_lower_bound": (-1.5, -3.14),\
"input_upper_bound": [1.5, 3.14], "input_upper_bound": (1.5, 3.14),\
"car_size": 0.2,\
"wheel_size": (0.075, 0.015)
} }
super(TwoWheeledConstEnv, self).__init__(self.config) super(TwoWheeledConstEnv, self).__init__(self.config)
@ -99,4 +104,279 @@ class TwoWheeledConstEnv(Env):
return next_x.flatten(), costs, \ return next_x.flatten(), costs, \
self.step_count > self.config["max_step"], \ self.step_count > self.config["max_step"], \
{"goal_state" : self.g_x} {"goal_state" : self.g_x}
def plot_func(self, to_plot, i=None, history_x=None, history_g_x=None):
""" plot cartpole object function
Args:
to_plot (axis or imgs): plotted objects
i (int): frame count
history_x (numpy.ndarray): history of state, shape(iters, state)
history_g_x (numpy.ndarray): history of goal state,
shape(iters, state)
Returns:
None or imgs : imgs order is ["cart_img", "pole_img"]
"""
if isinstance(to_plot, Axes):
imgs = {} # create new imgs
imgs["car"] = to_plot.plot([], [], c="k")[0]
imgs["car_angle"] = to_plot.plot([], [], c="k")[0]
imgs["left_tire"] = to_plot.plot([], [], c="k", linewidth=5)[0]
imgs["right_tire"] = to_plot.plot([], [], c="k", linewidth=5)[0]
imgs["goal"] = to_plot.plot([], [], marker="*",
c="b", markersize=10)[0]
imgs["traj"] = to_plot.plot([], [], c="b", linestyle="dashed")[0]
# set axis
to_plot.set_xlim([-1., 3.])
to_plot.set_ylim([-1., 3.])
return imgs
# set imgs
# car imgs
car_x, car_y, car_angle_x, car_angle_y, \
left_tire_x, left_tire_y, right_tire_x, right_tire_y = \
self._plot_car(history_x[i])
to_plot["car"].set_data(car_x, car_y)
to_plot["car_angle"].set_data(car_angle_x, car_angle_y)
to_plot["left_tire"].set_data(left_tire_x, left_tire_y,)
to_plot["right_tire"].set_data(right_tire_x, right_tire_y)
# goal and trajs
to_plot["goal"].set_data(history_g_x[i, 0], history_g_x[i, 1])
to_plot["traj"].set_data(history_x[:i, 0], history_x[:i, 1])
def _plot_car(self, curr_x):
""" plot car fucntions
"""
# cart
car_x, car_y, car_angle_x, car_angle_y = \
circle_with_angle(curr_x[0], curr_x[1],
self.config["car_size"], curr_x[2])
# left tire
center_x = (self.config["car_size"] \
+ self.config["wheel_size"][1]) \
* np.cos(curr_x[2]-np.pi/2.) + curr_x[0]
center_y = (self.config["car_size"] \
+ self.config["wheel_size"][1]) \
* np.sin(curr_x[2]-np.pi/2.) + curr_x[1]
left_tire_x, left_tire_y = \
square(center_x, center_y,
self.config["wheel_size"], curr_x[2])
# right tire
center_x = (self.config["car_size"] \
+ self.config["wheel_size"][1]) \
* np.cos(curr_x[2]+np.pi/2.) + curr_x[0]
center_y = (self.config["car_size"] \
+ self.config["wheel_size"][1]) \
* np.sin(curr_x[2]+np.pi/2.) + curr_x[1]
right_tire_x, right_tire_y = \
square(center_x, center_y,
self.config["wheel_size"], curr_x[2])
return car_x, car_y, car_angle_x, car_angle_y,\
left_tire_x, left_tire_y,\
right_tire_x, right_tire_y
class TwoWheeledTrackEnv(Env):
""" Two wheeled robot with constant goal Env
"""
def __init__(self):
"""
"""
self.config = {"state_size" : 3,\
"input_size" : 2,\
"dt" : 0.01,\
"max_step" : 1000,\
"input_lower_bound": (-1.5, -3.14),\
"input_upper_bound": (1.5, 3.14),\
"car_size": 0.2,\
"wheel_size": (0.075, 0.015)
}
super(TwoWheeledTrackEnv, self).__init__(self.config)
@staticmethod
def make_road(linelength=3., circle_radius=1.):
""" make track
Returns:
road (numpy.ndarray): road info, shape(n_point, 3) x, y, angle
"""
# line
# not include start points
line = np.linspace(-1.5, 1.5, num=51, endpoint=False)[1:]
line_1 = np.stack((line, np.zeros(50)), axis=1)
line_2 = np.stack((line[::-1], np.zeros(50)+circle_radius*2.), axis=1)
# circle
circle_1_x, circle_1_y = circle(linelength/2., circle_radius,
circle_radius, start=-np.pi/2., end=np.pi/2., n_point=50)
circle_1 = np.stack((circle_1_x , circle_1_y), axis=1)
circle_2_x, circle_2_y = circle(-linelength/2., circle_radius,
circle_radius, start=np.pi/2., end=3*np.pi/2., n_point=50)
circle_2 = np.stack((circle_2_x , circle_2_y), axis=1)
road_pos = np.concatenate((line_1, circle_1, line_2, circle_2), axis=0)
# calc road angle
road_diff = road_pos[1:] - road_pos[:-1]
road_angle = np.arctan2(road_diff[:, 1], road_diff[:, 0])
road_angle = np.concatenate((np.zeros(1), road_angle))
road = np.concatenate((road_pos, road_angle[:, np.newaxis]), axis=1)
return np.tile(road, (3, 1))
def reset(self, init_x=None):
""" reset state
Returns:
init_x (numpy.ndarray): initial state, shape(state_size, )
info (dict): information
"""
self.step_count = 0
self.curr_x = np.zeros(self.config["state_size"])
if init_x is not None:
self.curr_x = init_x
# goal
self.g_traj = self.make_road()
# clear memory
self.history_x = []
self.history_g_x = []
return self.curr_x, {"goal_state": self.g_traj}
def step(self, u):
""" step environments
Args:
u (numpy.ndarray) : input, shape(input_size, )
Returns:
next_x (numpy.ndarray): next state, shape(state_size, )
cost (float): costs
done (bool): end the simulation or not
info (dict): information
"""
# clip action
u = np.clip(u,
self.config["input_lower_bound"],
self.config["input_upper_bound"])
# step
next_x = step_two_wheeled_env(self.curr_x, u, self.config["dt"])
costs = 0.
costs += 0.1 * np.sum(u**2)
costs += np.min(np.linalg.norm(self.curr_x - self.g_traj, axis=1))
# save history
self.history_x.append(next_x.flatten())
# update
self.curr_x = next_x.flatten()
# update costs
self.step_count += 1
return next_x.flatten(), costs, \
self.step_count > self.config["max_step"], \
{"goal_state" : self.g_traj}
def plot_func(self, to_plot, i=None, history_x=None, history_g_x=None):
""" plot cartpole object function
Args:
to_plot (axis or imgs): plotted objects
i (int): frame count
history_x (numpy.ndarray): history of state, shape(iters, state)
history_g_x (numpy.ndarray): history of goal state,
shape(iters, state)
Returns:
None or imgs : imgs order is ["cart_img", "pole_img"]
"""
if isinstance(to_plot, Axes):
imgs = {} # create new imgs
imgs["car"] = to_plot.plot([], [], c="k")[0]
imgs["car_angle"] = to_plot.plot([], [], c="k")[0]
imgs["left_tire"] = to_plot.plot([], [], c="k", linewidth=5)[0]
imgs["right_tire"] = to_plot.plot([], [], c="k", linewidth=5)[0]
imgs["goal"] = to_plot.plot([], [], marker="*",
c="b", markersize=10)[0]
imgs["traj"] = to_plot.plot([], [], c="b", linestyle="dashed")[0]
# set axis
to_plot.set_xlim([-3., 3.])
to_plot.set_ylim([-1., 3.])
# plot road
to_plot.plot(self.g_traj[:, 0], self.g_traj[:, 1],
c="k", linestyle="dashed")
return imgs
# set imgs
# car imgs
car_x, car_y, car_angle_x, car_angle_y, \
left_tire_x, left_tire_y, right_tire_x, right_tire_y = \
self._plot_car(history_x[i])
to_plot["car"].set_data(car_x, car_y)
to_plot["car_angle"].set_data(car_angle_x, car_angle_y)
to_plot["left_tire"].set_data(left_tire_x, left_tire_y,)
to_plot["right_tire"].set_data(right_tire_x, right_tire_y)
# goal and trajs
to_plot["goal"].set_data(history_g_x[i, 0], history_g_x[i, 1])
to_plot["traj"].set_data(history_x[:i, 0], history_x[:i, 1])
def _plot_car(self, curr_x):
""" plot car fucntions
"""
# cart
car_x, car_y, car_angle_x, car_angle_y = \
circle_with_angle(curr_x[0], curr_x[1],
self.config["car_size"], curr_x[2])
# left tire
center_x = (self.config["car_size"] \
+ self.config["wheel_size"][1]) \
* np.cos(curr_x[2]-np.pi/2.) + curr_x[0]
center_y = (self.config["car_size"] \
+ self.config["wheel_size"][1]) \
* np.sin(curr_x[2]-np.pi/2.) + curr_x[1]
left_tire_x, left_tire_y = \
square(center_x, center_y,
self.config["wheel_size"], curr_x[2])
# right tire
center_x = (self.config["car_size"] \
+ self.config["wheel_size"][1]) \
* np.cos(curr_x[2]+np.pi/2.) + curr_x[0]
center_y = (self.config["car_size"] \
+ self.config["wheel_size"][1]) \
* np.sin(curr_x[2]+np.pi/2.) + curr_x[1]
right_tire_x, right_tire_y = \
square(center_x, center_y,
self.config["wheel_size"], curr_x[2])
return car_x, car_y, car_angle_x, car_angle_y,\
left_tire_x, left_tire_y,\
right_tire_x, right_tire_y

View File

@ -6,7 +6,7 @@ def make_model(args, config):
if args.env == "FirstOrderLag": if args.env == "FirstOrderLag":
return FirstOrderLagModel(config) return FirstOrderLagModel(config)
elif args.env == "TwoWheeledConst" or args.env == "TwoWheeled": elif args.env == "TwoWheeledConst" or args.env == "TwoWheeledTrack":
return TwoWheeledModel(config) return TwoWheeledModel(config)
elif args.env == "CartPole": elif args.env == "CartPole":
return CartPoleModel(config) return CartPoleModel(config)

View File

@ -0,0 +1,39 @@
import numpy as np
from .planner import Planner
class ClosestPointPlanner(Planner):
""" This planner make goal state according to goal path
"""
def __init__(self, config):
"""
"""
super(ClosestPointPlanner, self).__init__()
self.pred_len = config.PRED_LEN
self.state_size = config.STATE_SIZE
self.n_ahead = config.N_AHEAD
def plan(self, curr_x, g_traj):
"""
Args:
curr_x (numpy.ndarray): current state, shape(state_size)
g_x (numpy.ndarray): goal state, shape(state_size),
this state should be obtained from env
Returns:
g_xs (numpy.ndarrya): goal state, shape(pred_len+1, state_size)
"""
min_idx = np.argmin(np.linalg.norm(curr_x[:-1] - g_traj[:, :-1],
axis=1))
start = (min_idx+self.n_ahead)
if start > len(g_traj):
start = len(g_traj)
end = min_idx+self.n_ahead+self.pred_len+1
if (min_idx+self.n_ahead+self.pred_len+1) > len(g_traj):
end = len(g_traj)
if abs(start - end) != self.pred_len + 1:
return np.tile(g_traj[-1], (self.pred_len+1, 1))
return g_traj[start:end]

View File

@ -1,7 +1,7 @@
import numpy as np import numpy as np
from .planner import Planner from .planner import Planner
class ConstantPlanner(): class ConstantPlanner(Planner):
""" This planner make constant goal state """ This planner make constant goal state
""" """
def __init__(self, config): def __init__(self, config):

View File

@ -1,8 +1,15 @@
from .const_planner import ConstantPlanner from .const_planner import ConstantPlanner
from .closest_point_planner import ClosestPointPlanner
def make_planner(args, config): def make_planner(args, config):
if args.planner_type == "const": if args.env == "FirstOrderLag":
return ConstantPlanner(config)
elif args.env == "TwoWheeledConst":
return ConstantPlanner(config)
elif args.env == "TwoWheeledTrack":
return ClosestPointPlanner(config)
elif args.env == "CartPole":
return ConstantPlanner(config) return ConstantPlanner(config)
raise NotImplementedError("There is not {} Planner".format(args.planner_type)) raise NotImplementedError("There is not {} Planner".format(args.planner_type))

View File

@ -0,0 +1,74 @@
import os
from logging import getLogger
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
logger = getLogger(__name__)
class Animator():
""" animation class
"""
def __init__(self, args, env):
"""
"""
self.env_name = args.env
self.result_dir = args.result_dir
self.controller_type = args.controller_type
self.interval = env.config["dt"] * 1000. # to ms
self.plot_func = env.plot_func
self.imgs = []
def _setup(self):
""" set up figure of animation
"""
# make fig
self.anim_fig = plt.figure()
# axis
self.axis = self.anim_fig.add_subplot(111)
self.axis.set_aspect('equal', adjustable='box')
self.imgs = self.plot_func(self.axis)
def _update_img(self, i, history_x, history_g_x):
""" update animation
Args:
i (int): frame count
history_x (numpy.ndarray): history of state,
shape(iters, state_size)
history_g (numpy.ndarray): history of goal state,
shape(iters, input_size)
"""
self.plot_func(self.imgs, i, history_x, history_g_x)
def draw(self, history_x, history_g_x=None):
"""draw the animation and save
Args:
history_x (numpy.ndarray): history of state,
shape(iters, state_size)
history_g (numpy.ndarray): history of goal state,
shape(iters, input_size)
Returns:
None
"""
# set up animation figures
self._setup()
_update_img = lambda i: self._update_img(i, history_x, history_g_x)
# call funcanimation
animation = FuncAnimation(
self.anim_fig,
_update_img, interval=self.interval, frames=len(history_x)-1)
# save animation
path = os.path.join(self.result_dir, self.controller_type,
"animation-" + self.env_name + ".mp4")
logger.info("Saved Animation to {} ...".format(path))
animation.save(path, writer="ffmpeg")

View File

@ -0,0 +1,99 @@
import os
import numpy as np
import matplotlib.pyplot as plt
from ..common.utils import rotate_pos
def circle(center_x, center_y, radius, start=0., end=2*np.pi, n_point=100):
""" Create circle matrix
Args:
center_x (float): the center x position of the circle
center_y (float): the center y position of the circle
radius (float): in meters
start (float): start angle
end (float): end angle
Returns:
circle x : numpy.ndarray
circle y : numpy.ndarray
"""
diff = end - start
circle_xs = []
circle_ys = []
for i in range(n_point + 1):
circle_xs.append(center_x + radius * np.cos(i*diff/n_point + start))
circle_ys.append(center_y + radius * np.sin(i*diff/n_point + start))
return np.array(circle_xs), np.array(circle_ys)
def circle_with_angle(center_x, center_y, radius, angle):
""" Create circle matrix with angle line matrix
Args:
center_x (float): the center x position of the circle
center_y (float): the center y position of the circle
radius (float): in meters
angle (float): in radians
Returns:
circle_x (numpy.ndarray): x data of circle
circle_y (numpy.ndarray): y data of circle
angle_x (numpy.ndarray): x data of circle angle
angle_y (numpy.ndarray): y data of circle angle
"""
circle_x, circle_y = circle(center_x, center_y, radius)
angle_x = np.array([center_x, center_x + np.cos(angle) * radius])
angle_y = np.array([center_y, center_y + np.sin(angle) * radius])
return circle_x, circle_y, angle_x, angle_y
def square(center_x, center_y, shape, angle):
""" Create square
Args:
center_x (float): the center x position of the square
center_y (float): the center y position of the square
shape (tuple): the square's shape(width/2, height/2)
angle (float): in radians
Returns:
square_x (numpy.ndarray): shape(5, ), counterclockwise from right-up
square_y (numpy.ndarray): shape(5, ), counterclockwise from right-up
"""
# start with the up right points
# create point in counterclockwise, local
square_xy = np.array([[shape[0], shape[1]],
[-shape[0], shape[1]],
[-shape[0], -shape[1]],
[shape[0], -shape[1]],
[shape[0], shape[1]]])
# translate position to world
# rotation
trans_points = rotate_pos(square_xy, angle)
# translation
trans_points += np.array([center_x, center_y])
return trans_points[:, 0], trans_points[:, 1]
def square_with_angle(center_x, center_y, shape, angle):
""" Create square with angle line
Args:
center_x (float): the center x position of the square
center_y (float): the center y position of the square
shape (tuple): the square's shape(width/2, height/2)
angle (float): in radians
Returns:
square_x (numpy.ndarray): shape(5, ), counterclockwise from right-up
square_y (numpy.ndarray): shape(5, ), counterclockwise from right-up
angle_x (numpy.ndarray): x data of square angle
angle_y (numpy.ndarray): y data of square angle
"""
square_x, square_y = square(center_x, center_y, shape, angle)
angle_x = np.array([center_x, center_x + np.cos(angle) * shape[0]])
angle_y = np.array([center_y, center_y + np.sin(angle) * shape[1]])
return square_x, square_y, angle_x, angle_y

View File

@ -29,7 +29,7 @@ class ExpRunner():
while not done: while not done:
logger.debug("Step = {}".format(step_count)) logger.debug("Step = {}".format(step_count))
# plan # plan
g_xs = planner.plan(curr_x, g_x=info["goal_state"]) g_xs = planner.plan(curr_x, info["goal_state"])
# obtain sol # obtain sol
u = controller.obtain_sol(curr_x, g_xs) u = controller.obtain_sol(curr_x, g_xs)

View File

@ -5,8 +5,13 @@
# PythonLinearNonLinearControl # PythonLinearNonLinearControl
PythonLinearNonLinearControl is a library implementing the linear and nonlinear control theories in python. PythonLinearNonLinearControl is a library implementing the linear and nonlinear control theories in python.
Due to use only basic libralies (scipy, numpy), this library is also easily to extend for your own situations.
<img src="assets/concept.png" width="500"> <div><img src="assets/concept.png" width="500"/></div>
<img src="assets/cartpole.gif" width="350">
<img src="assets/twowheeledconst.gif" width="350">
<img src="assets/twowheeledtrack.gif" width="350">
# Algorithms # Algorithms
@ -24,7 +29,6 @@ PythonLinearNonLinearControl is a library implementing the linear and nonlinear
| Constrained Nonlinear Model Predictive Control Newton (NMPC-Newton) | x | ✓ | x | x | x | | Constrained Nonlinear Model Predictive Control Newton (NMPC-Newton) | x | ✓ | x | x | x |
"Need Gradient" means that you have to implement the gradient of the model or the gradient of hamiltonian. "Need Gradient" means that you have to implement the gradient of the model or the gradient of hamiltonian.
This library is also easily to extend for your own situations.
Following algorithms are implemented in PythonLinearNonlinearControl Following algorithms are implemented in PythonLinearNonlinearControl
@ -61,11 +65,13 @@ Following algorithms are implemented in PythonLinearNonlinearControl
# Environments # Environments
There are 4 example environments, "FirstOrderLag", "TwoWheeledConst", "TwoWheeledTrack" and "Cartpole".
| Name | Linear | Nonlinear | State Size | Input size | | Name | Linear | Nonlinear | State Size | Input size |
|:----------|:---------------:|:----------------:|:----------------:|:----------------:| |:----------|:---------------:|:----------------:|:----------------:|:----------------:|
| First Order Lag System | ✓ | x | 4 | 2 | | First Order Lag System | ✓ | x | 4 | 2 |
| Two wheeled System (Constant Goal) | x | ✓ | 3 | 2 | | Two wheeled System (Constant Goal) | x | ✓ | 3 | 2 |
| Two wheeled System (Moving Goal) (Coming soon) | x | ✓ | 3 | 2 | | Two wheeled System (Moving Goal) | x | ✓ | 3 | 2 |
| Cartpole (Swing up) | x | ✓ | 4 | 1 | | Cartpole (Swing up) | x | ✓ | 4 | 1 |
All states and inputs of environments are continuous. All states and inputs of environments are continuous.
@ -104,7 +110,7 @@ pip install -e .
You can run the experiments as follows: You can run the experiments as follows:
``` ```
python scripts/simple_run.py --env first-order_lag --controller CEM python scripts/simple_run.py --env FirstOrderLag --controller CEM
``` ```
**figures and animations are saved in the ./result folder.** **figures and animations are saved in the ./result folder.**
@ -147,9 +153,9 @@ Coming soon !!
# Requirements # Requirements
- numpy - numpy
- matplotlib
- cvxopt
- scipy - scipy
- matplotlib (for figures and animations)
- ffmpeg (for animations)
# License # License

BIN
assets/cartpole.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
assets/twowheeledconst.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

BIN
assets/twowheeledtrack.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

View File

@ -9,6 +9,7 @@ from PythonLinearNonlinearControl.envs.make_envs import make_env
from PythonLinearNonlinearControl.runners.make_runners import make_runner from PythonLinearNonlinearControl.runners.make_runners import make_runner
from PythonLinearNonlinearControl.plotters.plot_func import plot_results, \ from PythonLinearNonlinearControl.plotters.plot_func import plot_results, \
save_plot_data save_plot_data
from PythonLinearNonlinearControl.plotters.animator import Animator
def run(args): def run(args):
# logger # logger
@ -39,12 +40,16 @@ def run(args):
plot_results(args, history_x, history_u, history_g=history_g) plot_results(args, history_x, history_u, history_g=history_g)
save_plot_data(args, history_x, history_u, history_g=history_g) save_plot_data(args, history_x, history_u, history_g=history_g)
if args.save_anim:
animator = Animator(args, env)
animator.draw(history_x, history_g)
def main(): def main():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("--controller_type", type=str, default="DDP") parser.add_argument("--controller_type", type=str, default="CEM")
parser.add_argument("--planner_type", type=str, default="const") parser.add_argument("--env", type=str, default="TwoWheeledTrack")
parser.add_argument("--env", type=str, default="CartPole") parser.add_argument("--save_anim", type=bool_flag, default=1)
parser.add_argument("--result_dir", type=str, default="./result") parser.add_argument("--result_dir", type=str, default="./result")
args = parser.parse_args() args = parser.parse_args()