2019-11-23 23:45:54 +08:00
|
|
|
import numpy as np
|
|
|
|
from abc import ABC, abstractmethod
|
|
|
|
|
|
|
|
|
|
|
|
#########################
|
|
|
|
# Temporal State Vector #
|
|
|
|
#########################
|
|
|
|
|
|
|
|
class TemporalState:
|
2019-11-29 16:36:30 +08:00
|
|
|
def __init__(self, x, y, psi):
|
2019-11-23 23:45:54 +08:00
|
|
|
"""
|
2019-11-24 05:29:25 +08:00
|
|
|
Temporal State Vector containing car pose (x, y, psi) and velocity
|
2019-11-23 23:45:54 +08:00
|
|
|
:param x: x position in global coordinate system | [m]
|
|
|
|
:param y: y position in global coordinate system | [m]
|
|
|
|
:param psi: yaw angle | [rad]
|
|
|
|
:param v_x: velocity in x direction (car frame) | [m/s]
|
|
|
|
:param v_y: velocity in y direction (car frame) | [m/s]
|
|
|
|
"""
|
|
|
|
self.x = x
|
|
|
|
self.y = y
|
|
|
|
self.psi = psi
|
|
|
|
|
|
|
|
|
|
|
|
########################
|
|
|
|
# Spatial State Vector #
|
|
|
|
########################
|
|
|
|
|
|
|
|
class SpatialState(ABC):
|
2019-11-24 05:29:25 +08:00
|
|
|
"""
|
|
|
|
Spatial State Vector - Abstract Base Class.
|
|
|
|
"""
|
|
|
|
|
2019-11-23 23:45:54 +08:00
|
|
|
@abstractmethod
|
|
|
|
def __init__(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def __getitem__(self, item):
|
|
|
|
return list(vars(self).values())[item]
|
|
|
|
|
2019-11-29 16:36:30 +08:00
|
|
|
def __setitem__(self, key, value):
|
|
|
|
vars(self)[list(vars(self).keys())[key]] = value
|
|
|
|
|
2019-11-23 23:45:54 +08:00
|
|
|
def __len__(self):
|
|
|
|
return len(vars(self))
|
|
|
|
|
|
|
|
def __iadd__(self, other):
|
2019-11-24 05:29:25 +08:00
|
|
|
"""
|
|
|
|
Overload Sum-Add operator.
|
|
|
|
:param other: numpy array to be added to state vector
|
|
|
|
"""
|
|
|
|
|
2019-11-23 23:45:54 +08:00
|
|
|
for state_id, state in enumerate(vars(self).values()):
|
|
|
|
vars(self)[list(vars(self).keys())[state_id]] += other[state_id]
|
|
|
|
return self
|
|
|
|
|
2019-11-24 05:29:25 +08:00
|
|
|
def list_states(self):
|
|
|
|
"""
|
|
|
|
Return list of names of all states.
|
|
|
|
"""
|
|
|
|
return list(vars(self).keys())
|
|
|
|
|
2019-11-23 23:45:54 +08:00
|
|
|
|
|
|
|
class SimpleSpatialState(SpatialState):
|
2019-11-29 16:36:30 +08:00
|
|
|
def __init__(self, e_y, e_psi, t):
|
2019-11-23 23:45:54 +08:00
|
|
|
"""
|
2019-11-24 05:29:25 +08:00
|
|
|
Simplified Spatial State Vector containing orthogonal deviation from
|
|
|
|
reference path (e_y), difference in orientation (e_psi) and velocity
|
2019-11-23 23:45:54 +08:00
|
|
|
:param e_y: orthogonal deviation from center-line | [m]
|
|
|
|
:param e_psi: yaw angle relative to path | [rad]
|
2019-11-29 16:36:30 +08:00
|
|
|
:param t: time | [s]
|
2019-11-23 23:45:54 +08:00
|
|
|
"""
|
|
|
|
super(SimpleSpatialState, self).__init__()
|
|
|
|
|
|
|
|
self.e_y = e_y
|
|
|
|
self.e_psi = e_psi
|
|
|
|
self.t = t
|
|
|
|
|
|
|
|
|
|
|
|
####################################
|
|
|
|
# Spatial Bicycle Model Base Class #
|
|
|
|
####################################
|
|
|
|
|
|
|
|
class SpatialBicycleModel(ABC):
|
|
|
|
def __init__(self, reference_path):
|
|
|
|
"""
|
2019-11-24 05:29:25 +08:00
|
|
|
Abstract Base Class for Spatial Reformulation of Bicycle Model.
|
|
|
|
:param reference_path: reference path object to follow
|
2019-11-23 23:45:54 +08:00
|
|
|
"""
|
|
|
|
|
|
|
|
# Precision
|
|
|
|
self.eps = 1e-12
|
|
|
|
|
|
|
|
# Reference Path
|
|
|
|
self.reference_path = reference_path
|
|
|
|
|
2019-11-24 05:29:25 +08:00
|
|
|
# Set initial distance traveled
|
2019-11-23 23:45:54 +08:00
|
|
|
self.s = 0.0
|
|
|
|
|
2019-11-24 05:29:25 +08:00
|
|
|
# Set initial waypoint ID
|
2019-11-23 23:45:54 +08:00
|
|
|
self.wp_id = 0
|
|
|
|
|
2019-11-24 05:29:25 +08:00
|
|
|
# Set initial waypoint
|
2019-11-23 23:45:54 +08:00
|
|
|
self.current_waypoint = self.reference_path.waypoints[self.wp_id]
|
|
|
|
|
2019-11-24 05:29:25 +08:00
|
|
|
# Declare spatial state variable | Initialization in sub-class
|
2019-11-23 23:45:54 +08:00
|
|
|
self.spatial_state = None
|
|
|
|
|
2019-11-24 05:29:25 +08:00
|
|
|
# Declare temporal state variable | Initialization in sub-class
|
|
|
|
self.temporal_state = None
|
|
|
|
|
|
|
|
# Declare system matrices of linearized model | Used for MPC
|
|
|
|
self.A, self.B = None, None
|
|
|
|
|
|
|
|
def s2t(self, reference_waypoint=None, reference_state=None):
|
2019-11-23 23:45:54 +08:00
|
|
|
"""
|
2019-11-24 05:29:25 +08:00
|
|
|
Convert spatial state to temporal state. Either convert self.spatial_
|
2019-11-23 23:45:54 +08:00
|
|
|
state with current waypoint as reference or provide reference waypoint
|
2019-11-24 05:29:25 +08:00
|
|
|
and reference_state.
|
2019-11-23 23:45:54 +08:00
|
|
|
:return x, y, psi
|
|
|
|
"""
|
|
|
|
|
|
|
|
# Compute spatial state for current waypoint if no waypoint given
|
2019-11-24 05:29:25 +08:00
|
|
|
if reference_waypoint is None and reference_state is None:
|
2019-11-23 23:45:54 +08:00
|
|
|
|
|
|
|
# compute temporal state variables
|
|
|
|
x = self.current_waypoint.x - self.spatial_state.e_y * np.sin(
|
|
|
|
self.current_waypoint.psi)
|
|
|
|
y = self.current_waypoint.y + self.spatial_state.e_y * np.cos(
|
|
|
|
self.current_waypoint.psi)
|
|
|
|
psi = self.current_waypoint.psi + self.spatial_state.e_psi
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
# compute temporal state variables
|
2019-11-24 05:29:25 +08:00
|
|
|
x = reference_waypoint.x - reference_state[0] * np.sin(
|
2019-11-23 23:45:54 +08:00
|
|
|
reference_waypoint.psi)
|
2019-11-24 05:29:25 +08:00
|
|
|
y = reference_waypoint.y + reference_state[0] * np.cos(
|
2019-11-23 23:45:54 +08:00
|
|
|
reference_waypoint.psi)
|
2019-11-24 05:29:25 +08:00
|
|
|
psi = reference_waypoint.psi + reference_state[1]
|
2019-11-23 23:45:54 +08:00
|
|
|
|
|
|
|
return x, y, psi
|
|
|
|
|
2019-11-29 16:36:30 +08:00
|
|
|
def drive(self, input, state=None, kappa=None, delta_s=None):
|
2019-11-23 23:45:54 +08:00
|
|
|
"""
|
2019-11-29 16:36:30 +08:00
|
|
|
Drive.
|
|
|
|
:param state: state vector for which to compute derivatives
|
|
|
|
:param input: input vector
|
|
|
|
:param kappa: curvature of corresponding waypoint
|
|
|
|
:return: numpy array with spatial derivatives for all state variables
|
2019-11-23 23:45:54 +08:00
|
|
|
"""
|
|
|
|
|
2019-11-24 05:29:25 +08:00
|
|
|
# Get spatial derivatives
|
2019-11-29 16:36:30 +08:00
|
|
|
if state is None and kappa is None and delta_s is None:
|
|
|
|
state = np.array(self.spatial_state[:])
|
|
|
|
# Get delta_s | distance to next waypoint
|
|
|
|
next_waypoint = self.reference_path.waypoints[self.wp_id + 1]
|
|
|
|
delta_s = next_waypoint - self.current_waypoint
|
|
|
|
# Get current curvature
|
|
|
|
kappa = self.current_waypoint.kappa
|
2019-11-23 23:45:54 +08:00
|
|
|
|
2019-11-29 16:36:30 +08:00
|
|
|
spatial_derivatives = self.get_spatial_derivatives(state, input, kappa)
|
2019-11-23 23:45:54 +08:00
|
|
|
|
2019-11-29 16:36:30 +08:00
|
|
|
# Update spatial state (Forward Euler Approximation)
|
|
|
|
self.spatial_state += spatial_derivatives * delta_s
|
2019-11-23 23:45:54 +08:00
|
|
|
|
2019-11-29 16:36:30 +08:00
|
|
|
# Assert that unique projections of car pose onto path exists
|
|
|
|
#assert self.spatial_state.e_y < (1 / (self.current_waypoint.kappa +
|
|
|
|
# self.eps))
|
2019-11-23 23:45:54 +08:00
|
|
|
|
2019-11-29 16:36:30 +08:00
|
|
|
# Increase waypoint ID
|
|
|
|
self.wp_id += 1
|
2019-11-23 23:45:54 +08:00
|
|
|
|
2019-11-29 16:36:30 +08:00
|
|
|
# Update current waypoint
|
|
|
|
self.current_waypoint = self.reference_path.waypoints[self.wp_id]
|
2019-11-23 23:45:54 +08:00
|
|
|
|
2019-11-29 16:36:30 +08:00
|
|
|
# Update temporal_state to match spatial state
|
|
|
|
self.temporal_state = self.s2t()
|
|
|
|
|
|
|
|
# Update s | total driven distance along path
|
|
|
|
self.s += delta_s
|
|
|
|
|
|
|
|
# Linearize model around new operating point
|
|
|
|
# self.A, self.B = self.linearize()
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
spatial_derivatives = self.get_spatial_derivatives(state, input,
|
|
|
|
kappa)
|
2019-11-23 23:45:54 +08:00
|
|
|
|
2019-11-29 16:36:30 +08:00
|
|
|
# Update spatial state (Forward Euler Approximation)
|
|
|
|
state += spatial_derivatives * delta_s
|
2019-11-23 23:45:54 +08:00
|
|
|
|
2019-11-29 16:36:30 +08:00
|
|
|
return state
|
2019-11-23 23:45:54 +08:00
|
|
|
|
|
|
|
@abstractmethod
|
2019-11-29 16:36:30 +08:00
|
|
|
def get_spatial_derivatives(self, state, input, kappa):
|
2019-11-23 23:45:54 +08:00
|
|
|
pass
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
def linearize(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2019-11-29 16:36:30 +08:00
|
|
|
#################
|
|
|
|
# Bicycle Model #
|
|
|
|
#################
|
2019-11-23 23:45:54 +08:00
|
|
|
|
2019-11-29 16:36:30 +08:00
|
|
|
class BicycleModel(SpatialBicycleModel):
|
|
|
|
def __init__(self, reference_path, e_y, e_psi, t):
|
2019-11-23 23:45:54 +08:00
|
|
|
"""
|
2019-11-24 05:29:25 +08:00
|
|
|
Simplified Spatial Bicycle Model. Spatial Reformulation of Kinematic
|
|
|
|
Bicycle Model. Uses Simplified Spatial State.
|
2019-11-23 23:45:54 +08:00
|
|
|
:param reference_path: reference path model is supposed to follow
|
2019-11-24 05:29:25 +08:00
|
|
|
:param e_y: deviation from reference path | [m]
|
|
|
|
:param e_psi: heading offset from reference path | [rad]
|
|
|
|
:param v: initial velocity | [m/s]
|
2019-11-23 23:45:54 +08:00
|
|
|
"""
|
2019-11-24 05:29:25 +08:00
|
|
|
|
|
|
|
# Initialize base class
|
2019-11-29 16:36:30 +08:00
|
|
|
super(BicycleModel, self).__init__(reference_path)
|
2019-11-23 23:45:54 +08:00
|
|
|
|
|
|
|
# Constants
|
2019-11-29 16:36:30 +08:00
|
|
|
self.l = 0.06
|
2019-11-23 23:45:54 +08:00
|
|
|
|
2019-11-24 05:29:25 +08:00
|
|
|
# Initialize spatial state
|
2019-11-29 16:36:30 +08:00
|
|
|
self.spatial_state = SimpleSpatialState(e_y, e_psi, t)
|
|
|
|
|
|
|
|
# Number of spatial state variables
|
|
|
|
self.n_states = len(self.spatial_state)
|
2019-11-23 23:45:54 +08:00
|
|
|
|
2019-11-24 05:29:25 +08:00
|
|
|
# Initialize temporal state
|
2019-11-23 23:45:54 +08:00
|
|
|
self.temporal_state = self.s2t()
|
|
|
|
|
2019-11-24 05:29:25 +08:00
|
|
|
# Compute linear system matrices | Used for MPC
|
2019-11-29 16:36:30 +08:00
|
|
|
# self.A, self.B = self.linearize()
|
2019-11-23 23:45:54 +08:00
|
|
|
|
2019-11-24 05:29:25 +08:00
|
|
|
def s2t(self, reference_waypoint=None, reference_state=None):
|
2019-11-23 23:45:54 +08:00
|
|
|
"""
|
2019-11-24 05:29:25 +08:00
|
|
|
Convert spatial state to temporal state. Either convert self.spatial_
|
|
|
|
state with current waypoint as reference or provide reference waypoint
|
|
|
|
and reference_state.
|
|
|
|
:return temporal state equivalent to self.spatial_state or provided
|
|
|
|
reference state
|
2019-11-23 23:45:54 +08:00
|
|
|
"""
|
|
|
|
|
2019-11-24 05:29:25 +08:00
|
|
|
if reference_state is None and reference_waypoint is None:
|
|
|
|
# Get pose information from base class implementation
|
2019-11-29 16:36:30 +08:00
|
|
|
x, y, psi = super(BicycleModel, self).s2t()
|
2019-11-24 05:29:25 +08:00
|
|
|
# Compute simplified velocities
|
2019-11-23 23:45:54 +08:00
|
|
|
else:
|
2019-11-24 05:29:25 +08:00
|
|
|
# Get pose information from base class implementation
|
2019-11-29 16:36:30 +08:00
|
|
|
x, y, psi = super(BicycleModel, self).s2t(reference_waypoint,
|
2019-11-24 05:29:25 +08:00
|
|
|
reference_state)
|
2019-11-23 23:45:54 +08:00
|
|
|
|
2019-11-29 16:36:30 +08:00
|
|
|
return TemporalState(x, y, psi)
|
2019-11-23 23:45:54 +08:00
|
|
|
|
2019-11-29 16:36:30 +08:00
|
|
|
def get_temporal_derivatives(self, state, input, kappa):
|
2019-11-23 23:45:54 +08:00
|
|
|
"""
|
2019-11-24 05:29:25 +08:00
|
|
|
Compute relevant temporal derivatives needed for state update.
|
2019-11-29 16:36:30 +08:00
|
|
|
:param state: state vector for which to compute derivatives
|
|
|
|
:param input: input vector
|
|
|
|
:param kappa: curvature of corresponding waypoint
|
2019-11-24 05:29:25 +08:00
|
|
|
:return: temporal derivatives of distance, angle and velocity
|
2019-11-23 23:45:54 +08:00
|
|
|
"""
|
|
|
|
|
2019-11-29 16:36:30 +08:00
|
|
|
e_y, e_psi, t = state
|
|
|
|
v, delta = input
|
2019-11-23 23:45:54 +08:00
|
|
|
|
2019-11-24 05:29:25 +08:00
|
|
|
# Compute velocity along path
|
2019-11-29 16:36:30 +08:00
|
|
|
s_dot = 1 / (1 - (e_y * kappa)) * v * np.cos(e_psi)
|
2019-11-23 23:45:54 +08:00
|
|
|
|
2019-11-24 05:29:25 +08:00
|
|
|
# Compute yaw angle rate of change
|
2019-11-29 16:36:30 +08:00
|
|
|
psi_dot = v / self.l * np.tan(delta)
|
2019-11-23 23:45:54 +08:00
|
|
|
|
2019-11-29 16:36:30 +08:00
|
|
|
return s_dot, psi_dot
|
2019-11-23 23:45:54 +08:00
|
|
|
|
2019-11-29 16:36:30 +08:00
|
|
|
def get_spatial_derivatives(self, state, input, kappa):
|
2019-11-23 23:45:54 +08:00
|
|
|
"""
|
|
|
|
Compute spatial derivatives of all state variables for update.
|
2019-11-29 16:36:30 +08:00
|
|
|
:param state: state vector for which to compute derivatives
|
|
|
|
:param input: input vector
|
|
|
|
:param kappa: curvature of corresponding waypoint
|
2019-11-24 05:29:25 +08:00
|
|
|
:return: numpy array with spatial derivatives for all state variables
|
2019-11-23 23:45:54 +08:00
|
|
|
"""
|
|
|
|
|
2019-11-29 16:36:30 +08:00
|
|
|
e_y, e_psi, t = state
|
|
|
|
v, delta = input
|
|
|
|
|
2019-11-24 05:29:25 +08:00
|
|
|
# Compute temporal derivatives
|
2019-11-29 16:36:30 +08:00
|
|
|
s_dot, psi_dot = self.get_temporal_derivatives(state, input, kappa)
|
2019-11-23 23:45:54 +08:00
|
|
|
|
2019-11-24 05:29:25 +08:00
|
|
|
# Compute spatial derivatives
|
2019-11-29 16:36:30 +08:00
|
|
|
d_e_y_d_s = v * np.sin(e_psi) / s_dot
|
|
|
|
d_e_psi_d_s = psi_dot / s_dot - kappa
|
|
|
|
d_t_d_s = 1 / s_dot
|
2019-11-23 23:45:54 +08:00
|
|
|
|
2019-11-29 16:36:30 +08:00
|
|
|
return np.array([d_e_y_d_s, d_e_psi_d_s, d_t_d_s])
|
2019-11-23 23:45:54 +08:00
|
|
|
|
2019-11-29 16:36:30 +08:00
|
|
|
def linearize(self, v=None, kappa_r=None, delta_s=None):
|
2019-11-23 23:45:54 +08:00
|
|
|
"""
|
|
|
|
Linearize the system equations around the current state and waypoint.
|
2019-11-29 16:36:30 +08:00
|
|
|
:param kappa_r: kappa of waypoint around which to linearize
|
|
|
|
"""
|
2019-11-24 05:29:25 +08:00
|
|
|
|
2019-11-29 16:36:30 +08:00
|
|
|
# Get linearization state
|
|
|
|
if kappa_r is None and delta_s is None:
|
|
|
|
# Get curvature of linearization waypoint
|
|
|
|
kappa_r = self.reference_path.waypoints[self.wp_id].kappa
|
|
|
|
# Get delta_s
|
|
|
|
next_waypoint = self.reference_path.waypoints[self.wp_id + 1]
|
|
|
|
delta_s = next_waypoint - self.current_waypoint
|
2019-11-24 05:29:25 +08:00
|
|
|
|
|
|
|
###################
|
|
|
|
# System Matrices #
|
|
|
|
###################
|
|
|
|
|
2019-11-29 16:36:30 +08:00
|
|
|
# Construct Jacobian Matrix
|
|
|
|
a_1 = np.array([1, delta_s, 0])
|
|
|
|
a_2 = np.array([-kappa_r**2*delta_s, 1, 0])
|
|
|
|
a_3 = np.array([-kappa_r/v*delta_s, 0, 0])
|
2019-11-23 23:45:54 +08:00
|
|
|
|
2019-11-29 16:36:30 +08:00
|
|
|
b_1 = np.array([0, ])
|
|
|
|
b_2 = np.array([delta_s, ])
|
|
|
|
b_3 = np.array([0, ])
|
2019-11-23 23:45:54 +08:00
|
|
|
|
2019-11-29 16:36:30 +08:00
|
|
|
A = np.stack((a_1, a_2, a_3), axis=0)
|
|
|
|
B = np.stack((b_1, b_2, b_3), axis=0)
|
2019-11-23 23:45:54 +08:00
|
|
|
|
2019-11-29 16:36:30 +08:00
|
|
|
return A, B
|