"""
Dynamics Model
==============
This module contains a set of models to explain the truck dynamics. The way in which dynamics are regularly described are:
.. math::
x_{k+1} = f(x_k)
"""
# ============================================================================
# STANDARD IMPORTS
# ============================================================================
import typing
import numpy as np
from dataclasses import dataclass, field
from ctypes import c_double, c_int, byref
# ============================================================================
# INTERNAL IMPORTS
# ============================================================================
from ensemble.metaclass.dynamics import AbsDynamics
from ensemble.tools.exceptions import EnsembleAPIError
from ensemble.tools.screen import log_warning
from ensemble.tools import constants as ct
from ctypes import cdll, c_double, c_int8, c_uint8, c_bool, byref
from ensemble.tools.constants import (
DEFAULT_TRUCK_PATH,
DCT_RUNTIME_PARAM,
DCT_TRUCK_PARAM,
)
# ============================================================================
# CLASS AND DEFINITIONS
# ============================================================================
TIME_STEP = DCT_RUNTIME_PARAM["sampling_time_operational"]
TAU = DCT_TRUCK_PARAM["engineTau"]
LENGTH = DCT_TRUCK_PARAM["length"]
WIDTH = DCT_TRUCK_PARAM["width"]
MASS = DCT_TRUCK_PARAM["mass"]
INTERAXES = DCT_TRUCK_PARAM["interAxes"]
[docs]def dynamic_3rd_ego(state: np.ndarray, control: np.ndarray) -> np.ndarray:
"""Update vehicle state in 3rd order dynamics
Args:
state (np.ndarray): 3d-array @ k [position;speed;acceleration]
control (np.ndarray): 1d-array [control]
Returns:
np.ndarray: [3d-array] @ k+1 [position;speed;acceleration]
"""
K_a = TIME_STEP / TAU
A = np.array([[1, TIME_STEP, 0], [0, 1, TIME_STEP], [0, 0, (1 - K_a)],])
B = np.array([[0], [0], [K_a]])
return A @ state[:3] + B @ control[:1]
[docs]def dynamic_2nd_ego(state: np.ndarray, control: np.ndarray) -> np.ndarray:
"""Update vehicle state in 2nd order dynamics
Args:
state (np.ndarray): 2d-array @ k [position;speed]
control (np.ndarray): 1d-array [control]
Returns:
np.ndarray: [3d-array] @ k+1 [position;speed]
"""
A = np.array([[1, TIME_STEP], [0, 1]])
B = np.array([[0], [TIME_STEP]])
return A @ state[:2] + B @ control[:1]
[docs]@dataclass
class TruckDynamics(AbsDynamics):
"""External truck model"""
vehid: c_int = field(repr=True)
x: c_double = field(repr=True)
a: c_double = field(repr=True)
v: c_double = field(repr=True)
width: c_double = field(default=c_double(WIDTH), repr=False)
length: c_double = field(default=c_double(LENGTH), repr=False)
interAxes: c_double = field(default=c_double(INTERAXES), repr=False)
mass: c_double = field(default=c_double(MASS), repr=False)
library: str = field(default=DEFAULT_TRUCK_PATH, repr=False)
def __init__(self, vehid: int, x: float, v: float, a: float):
self.vehid = c_int(vehid)
self.x = c_double(x)
self.v = c_double(v)
self.a = c_double(a)
self.load_library(self.library)
self.getAcceleration(0)
[docs] def load_library(self, path_library):
"""Loads the truck library into the class"""
self.lib = cdll.LoadLibrary(path_library)
@property
def T(self):
return TIME_STEP
[docs] def getAcceleration(self, external_acc: float) -> np.ndarray:
"""Computes truck acceleration
Args:
external_acc (float): CACC/ACC commanded control
Returns:
np.ndarray: Truck acceleration for state computation.
"""
self.lib.TruckDynamics_dll(
self.vehid,
c_double(external_acc),
byref(self.x),
byref(self.v),
byref(self.a),
byref(self.interAxes),
byref(self.length),
byref(self.width),
byref(self.mass),
)
return np.array([self.x.value, self.v.value, self.a.value])
[docs] def __call__(self, state: np.ndarray, control: np.ndarray) -> np.ndarray:
"""Computes the dynamics passing through the truck
Args:
state (np.ndarray): Truck state @k (position, speed)
control (np.ndarray):
Returns:
np.ndarray:
"""
self.lib.TruckDynamics_dll(
self.vehid,
c_double(control[-1]),
byref(self.x),
byref(self.v),
byref(self.a),
byref(self.interAxes),
byref(self.length),
byref(self.width),
byref(self.mass),
)
return np.array([self.x.value, self.v.value, self.a.value])
[docs]@dataclass
class RegularDynamics(AbsDynamics):
vehid: c_int = field(repr=True)
x: c_double = field(repr=True)
a: c_double = field(repr=True)
v: c_double = field(repr=True)
width: c_double = field(default=c_double(WIDTH), repr=False)
length: c_double = field(default=c_double(LENGTH), repr=False)
interAxes: c_double = field(default=c_double(INTERAXES), repr=False)
mass: c_double = field(default=c_double(MASS), repr=False)
library: str = field(default=DEFAULT_TRUCK_PATH, repr=False)
def __init__(self, vehid: int, x: float, v: float, a: float):
self.vehid = c_int(vehid)
self.x = c_double(x)
self.v = c_double(v)
self.a = c_double(a)
# self.load_library(self.library)
# self.getAcceleration(0)
@property
def T(self):
return TIME_STEP
def __call__(self, state: np.ndarray, control: np.ndarray) -> np.ndarray:
return dynamic_3rd_ego(state, control)
[docs]@dataclass
class SampleDynamics(AbsDynamics):
@property
def T(self):
pass
def __call__(self, state: np.ndarray, control: np.ndarray):
log_warning("Calling non-existing dynamics")
raise EnsembleAPIError(
"Trying to call non existent dynamics. Try defining `RegularDynamics` or `TruckDynamics` from the dynamics module"
)
[docs]class PlatoonDynamics:
"""Potential class for handling vector state for platoons"""
if __name__ == "__main__":
# Run from base folder
import os
truck_path = os.path.join(
os.getcwd(), "ensemble", "libs", "darwin", "truckDynamics.dylib",
)
t = TruckDynamics(vehid=0, x=0, a=0, v=25)