Vehicle Dynamics Simulator (Barc Gym)

Jan 1, 2025 · 39 min read
projects

Graduate Student Instructor Project (Jan 2025 — May 2025)

  • Developed a lightweight vehicle dynamics simulator based on the dynamic bicycle model, providing standard OpenAI Gymnasium APIs for reinforcement learning and control tasks.
  • Implemented a connector to interface with the CARLA simulator, allowing seamless transition between simple dynamics models and high-fidelity physics simulations.
  • Created automated shell scripts for quick installation and setup.
  • Deployed the simulator on Google Colab to provide easy access for students in the vehicle dynamics class.

Vehicle_Dynamics_Simulator.ipynb

Python · Kernel: Python 3 · nbformat 4 · 40 cells

Download notebook
Markdown

Vehicle Dynamics Simulator

Contributors: Shengfan Cao, Edward Zhu, Thomas Fork.

In [ ]
%load_ext autoreload
%autoreload 2
Markdown

Install Dependencies

In [ ]
if "dependencies_installed" not in globals():
    !git clone https://github.com/MPC-Berkeley/barc_gym.git --depth 1
    !pip install -r barc_gym/requirements.txt
    !pip install -e barc_gym/gym-carla
    !pip install -e barc_gym/mpclab_common
    !pip install -e barc_gym/mpclab_controllers
    !pip install -e barc_gym/mpclab_simulation
    dependencies_installed = True
Cloning into 'barc_gym'...
remote: Enumerating objects: 73, done.
remote: Counting objects: 100% (73/73), done.
remote: Compressing objects: 100% (62/62), done.
remote: Total 73 (delta 7), reused 47 (delta 4), pack-reused 0 (from 0)
Receiving objects: 100% (73/73), 94.97 KiB | 2.97 MiB/s, done.
Resolving deltas: 100% (7/7), done.
Requirement already satisfied: numpy in /usr/local/lib/python3.11/dist-packages (from -r barc_gym/requirements.txt (line 2)) (2.0.2)
Requirement already satisfied: pyomo in /usr/local/lib/python3.11/dist-packages (from -r barc_gym/requirements.txt (line 3)) (6.8.2)
Collecting casadi (from -r barc_gym/requirements.txt (line 4))
  Downloading casadi-3.7.0-cp311-none-manylinux2014_x86_64.whl.metadata (2.2 kB)
Requirement already satisfied: matplotlib in /usr/local/lib/python3.11/dist-packages (from -r barc_gym/requirements.txt (line 5)) (3.10.0)
Requirement already satisfied: pyyaml in /usr/local/lib/python3.11/dist-packages (from -r barc_gym/requirements.txt (line 6)) (6.0.2)
Requirement already satisfied: tqdm in /usr/local/lib/python3.11/dist-packages (from -r barc_gym/requirements.txt (line 7)) (4.67.1)
Requirement already satisfied: pillow in /usr/local/lib/python3.11/dist-packages (from -r barc_gym/requirements.txt (line 8)) (11.1.0)
Collecting loguru (from -r barc_gym/requirements.txt (line 9))
  Downloading loguru-0.7.3-py3-none-any.whl.metadata (22 kB)
Requirement already satisfied: requests in /usr/local/lib/python3.11/dist-packages (from -r barc_gym/requirements.txt (line 10)) (2.32.3)
Requirement already satisfied: gymnasium in /usr/local/lib/python3.11/dist-packages (from -r barc_gym/requirements.txt (line 12)) (1.1.1)
Requirement already satisfied: pygame in /usr/local/lib/python3.11/dist-packages (from -r barc_gym/requirements.txt (line 13)) (2.6.1)
Requirement already satisfied: scikit-image in /usr/local/lib/python3.11/dist-packages (from -r barc_gym/requirements.txt (line 14)) (0.25.2)
Requirement already satisfied: ply in /usr/local/lib/python3.11/dist-packages (from pyomo->-r barc_gym/requirements.txt (line 3)) (3.11)
Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.11/dist-packages (from matplotlib->-r barc_gym/requirements.txt (line 5)) (1.3.2)
Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.11/dist-packages (from matplotlib->-r barc_gym/requirements.txt (line 5)) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.11/dist-packages (from matplotlib->-r barc_gym/requirements.txt (line 5)) (4.57.0)
Requirement already satisfied: kiwisolver>=1.3.1 in /usr/local/lib/python3.11/dist-packages (from matplotlib->-r barc_gym/requirements.txt (line 5)) (1.4.8)
Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.11/dist-packages (from matplotlib->-r barc_gym/requirements.txt (line 5)) (24.2)
Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.11/dist-packages (from matplotlib->-r barc_gym/requirements.txt (line 5)) (3.2.3)
Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.11/dist-packages (from matplotlib->-r barc_gym/requirements.txt (line 5)) (2.8.2)
Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.11/dist-packages (from requests->-r barc_gym/requirements.txt (line 10)) (3.4.1)
Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.11/dist-packages (from requests->-r barc_gym/requirements.txt (line 10)) (3.10)
Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.11/dist-packages (from requests->-r barc_gym/requirements.txt (line 10)) (2.3.0)
Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.11/dist-packages (from requests->-r barc_gym/requirements.txt (line 10)) (2025.1.31)
Requirement already satisfied: cloudpickle>=1.2.0 in /usr/local/lib/python3.11/dist-packages (from gymnasium->-r barc_gym/requirements.txt (line 12)) (3.1.1)
Requirement already satisfied: typing-extensions>=4.3.0 in /usr/local/lib/python3.11/dist-packages (from gymnasium->-r barc_gym/requirements.txt (line 12)) (4.13.2)
Requirement already satisfied: farama-notifications>=0.0.1 in /usr/local/lib/python3.11/dist-packages (from gymnasium->-r barc_gym/requirements.txt (line 12)) (0.0.4)
Requirement already satisfied: scipy>=1.11.4 in /usr/local/lib/python3.11/dist-packages (from scikit-image->-r barc_gym/requirements.txt (line 14)) (1.14.1)
Requirement already satisfied: networkx>=3.0 in /usr/local/lib/python3.11/dist-packages (from scikit-image->-r barc_gym/requirements.txt (line 14)) (3.4.2)
Requirement already satisfied: imageio!=2.35.0,>=2.33 in /usr/local/lib/python3.11/dist-packages (from scikit-image->-r barc_gym/requirements.txt (line 14)) (2.37.0)
Requirement already satisfied: tifffile>=2022.8.12 in /usr/local/lib/python3.11/dist-packages (from scikit-image->-r barc_gym/requirements.txt (line 14)) (2025.3.30)
Requirement already satisfied: lazy-loader>=0.4 in /usr/local/lib/python3.11/dist-packages (from scikit-image->-r barc_gym/requirements.txt (line 14)) (0.4)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.11/dist-packages (from python-dateutil>=2.7->matplotlib->-r barc_gym/requirements.txt (line 5)) (1.17.0)
Downloading casadi-3.7.0-cp311-none-manylinux2014_x86_64.whl (77.3 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 77.3/77.3 MB 9.8 MB/s eta 0:00:00
[?25hDownloading loguru-0.7.3-py3-none-any.whl (61 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 61.6/61.6 kB 3.5 MB/s eta 0:00:00
[?25hInstalling collected packages: loguru, casadi
Successfully installed casadi-3.7.0 loguru-0.7.3
Obtaining file:///content/barc_gym/gym-carla
  Preparing metadata (setup.py) ... [?25l[?25hdone
Requirement already satisfied: gymnasium in /usr/local/lib/python3.11/dist-packages (from gym_carla==0.0.1) (1.1.1)
Requirement already satisfied: pygame in /usr/local/lib/python3.11/dist-packages (from gym_carla==0.0.1) (2.6.1)
Requirement already satisfied: numpy>=1.21.0 in /usr/local/lib/python3.11/dist-packages (from gymnasium->gym_carla==0.0.1) (2.0.2)
Requirement already satisfied: cloudpickle>=1.2.0 in /usr/local/lib/python3.11/dist-packages (from gymnasium->gym_carla==0.0.1) (3.1.1)
Requirement already satisfied: typing-extensions>=4.3.0 in /usr/local/lib/python3.11/dist-packages (from gymnasium->gym_carla==0.0.1) (4.13.2)
Requirement already satisfied: farama-notifications>=0.0.1 in /usr/local/lib/python3.11/dist-packages (from gymnasium->gym_carla==0.0.1) (0.0.4)
Installing collected packages: gym_carla
  Running setup.py develop for gym_carla
Successfully installed gym_carla-0.0.1
Obtaining file:///content/barc_gym/mpclab_common
  Preparing metadata (setup.py) ... [?25l[?25hdone
Installing collected packages: mpclab_common
  Running setup.py develop for mpclab_common
Successfully installed mpclab_common-0.1
Obtaining file:///content/barc_gym/mpclab_controllers
  Preparing metadata (setup.py) ... [?25l[?25hdone
Installing collected packages: mpclab_controllers
  Running setup.py develop for mpclab_controllers
Successfully installed mpclab_controllers-0.1
Obtaining file:///content/barc_gym/mpclab_simulation
  Preparing metadata (setup.py) ... [?25l[?25hdone
Installing collected packages: mpclab_simulation
  Running setup.py develop for mpclab_simulation
Successfully installed mpclab_simulation-0.1
In [ ]
import site
import warnings
site.main()
warnings.filterwarnings("ignore", category=UserWarning)
warnings.filterwarnings("ignore", category=RuntimeWarning)
In [ ]
import numpy as np
import gymnasium as gym
import gym_carla
from matplotlib import pyplot as plt
from loguru import logger
Markdown

Part 0: Defining the base PID controller

**TODO: Reference Slides (slide 32) or the book (chapter 5.1) and lay out the formula. **

Markdown

Preliminary: Vehicle State definition

We follow the same vehicle state definition as the ROS messages we use in the hardware experiments. The following snippet shows the definition of fields that are the most relevant to your task.

@dataclass
class Position(PythonMsg):
    x: float = field(default=0)
    y: float = field(default=0)
    z: float = field(default=0)
    ...

@dataclass
class BodyLinearVelocity(PythonMsg):
    v_long: float = field(default=0)
    v_tran: float = field(default=0)
    v_n: float = field(default=0)

@dataclass
class BodyAngularVelocity(PythonMsg):
    w_phi: float = field(default=0)
    w_theta: float = field(default=0)
    w_psi: float = field(default=0)

@dataclass
class OrientationEuler(PythonMsg):
    phi: float = field(default=0)
    theta: float = field(default=0)
    psi: float = field(default=0)

@dataclass
class ParametricPose(PythonMsg):
    s: float = field(default=0)
    x_tran: float = field(default=0)
    n: float = field(default=0)
    e_psi: float = field(default=0)

@dataclass
class VehicleActuation(PythonMsg):
    t: float        = field(default=0)
    u_a: float      = field(default=0)
    u_steer: float  = field(default=0)
    ...

@dataclass
class VehicleState(PythonMsg):
    '''
    Complete vehicle state (local, global, and input)
    '''
    t: float                        = field(default=None)  # time in seconds
    x: Position                     = field(default=None)  # global position
    v: BodyLinearVelocity           = field(default=None)  # body linear velocity
    w: BodyAngularVelocity          = field(default=None)  # body angular velocity
    e: OrientationEuler             = field(default=None)  # global orientation (phi, theta, psi)
    p: ParametricPose               = field(default=None)  # parametric position (s, y, ths)
    u: VehicleActuation             = field(default=None)  # control inputs (u_a, u_steer)
    lap_num: int = field(default=None)
    ...

Use the dot notation to access an attribute of the data structure above. For example, to access the global y coordinate given a state object vehicle_state, you should do vehicle_state.x.y.

Markdown

Defining the PID Parameters

In [ ]
from mpclab_common.pytypes import PythonMsg
from dataclasses import dataclass, field


@dataclass
class PIDParams(PythonMsg):
    """
    This is a template for PID Parameters.
    Make sure you understand what each parameter means,
    but DON'T MODIFY ANYTHING HERE!
    """
    dt: float = field(default=0.1)  # The frequency of the controller
    Kp: float = field(default=2.0)  # The P component.
    Ki: float = field(default=0.0)  # The I component.
    Kd: float = field(default=0.0)  # The D component.

    # Constraints
    int_e_max: float = field(default=100)
    int_e_min: float = field(default=-100)
    u_max: float = field(default=None)
    u_min: float = field(default=None)
    du_max: float = field(default=None)
    du_min: float = field(default=None)

    # References
    u_ref: float = field(default=0.0)  # Input reference
    x_ref: float = field(default=0.0)  # PID state reference
Markdown

Implementing the base class for PID controller

TODO: Reference the slides or the book.

In [ ]
from typing import Tuple

class PID():
    '''
    Base class for PID controller
    Meant to be packaged for use in actual controller (eg. ones that operate directly on vehicle state) since a PID controller by itself is not sufficient for vehicle control
    See PIDLaneFollower for a PID controller that is an actual controller
    '''
    def __init__(self, params: PIDParams = PIDParams()):
        self.dt             = params.dt

        self.Kp             = params.Kp             # proportional gain
        self.Ki             = params.Ki             # integral gain
        self.Kd             = params.Kd             # derivative gain

        # Integral action and control action saturation limits
        self.int_e_max      = params.int_e_max
        self.int_e_min      = params.int_e_min
        self.u_max          = params.u_max
        self.u_min          = params.u_min
        self.du_max         = params.du_max
        self.du_min         = params.du_min

        self.x_ref          = params.x_ref  # PID State reference
        self.u_ref          = params.u_ref  # Input reference
        self.u_prev         = 0             # Internal buffer for previous input

        self.e              = 0             # error
        self.de             = 0             # finite time error difference
        self.ei             = 0             # accumulated error

        self.initialized = True

    def solve(self, x: float,
                u_prev: float = None) -> Tuple[float, dict]:
        if not self.initialized:
            raise(RuntimeError('PID controller is not initialized, run PID.initialize() before calling PID.solve()'))

        if self.u_prev is None and u_prev is None: u_prev = 0
        elif u_prev is None: u_prev = self.u_prev

        info = {'success' : True}

        # Compute error terms
        e_t = x - self.x_ref
        de_t = (e_t - self.e)/self.dt
        ei_t = self.ei + e_t*self.dt

        # Anti-windup
        if ei_t > self.int_e_max:
            ei_t = self.int_e_max
        elif ei_t < self.int_e_min:
            ei_t = self.int_e_min

        # Compute control action terms
        P_val  = self.Kp * e_t
        I_val  = self.Ki * ei_t
        D_val  = self.Kd * de_t

        u = -(P_val + I_val + D_val) + self.u_ref

        # Compute change in control action from previous timestep
        du = u - u_prev

        # Saturate change in control action
        if self.du_max is not None:
            du = self._saturate_rel_high(du)
        if self.du_min is not None:
            du = self._saturate_rel_low(du)

        u = du + u_prev

        # Saturate absolute control action
        if self.u_max is not None:
            u = self._saturate_abs_high(u)
        if self.u_min is not None:
            u = self._saturate_abs_low(u)

        # Update error terms
        self.e  = e_t
        self.de = de_t
        self.ei = ei_t

        self.u_prev = u
        return u, info

    def set_x_ref(self, x_ref: float):
        self.x_ref = x_ref
        # reset error integrator
        self.ei = 0
        # reset error, otherwise de/dt will skyrocket
        self.e = 0

    def set_u_ref(self, u_ref: float):
        self.u_ref = u_ref

    def clear_errors(self):
        self.ei = 0
        self.de = 0

    def set_params(self, params:  PIDParams):
        self.dt             = params.dt

        self.Kp             = params.Kp             # proportional gain
        self.Ki             = params.Ki             # integral gain
        self.Kd             = params.Kd             # derivative gain

        # Integral action and control action saturation limits
        self.int_e_max      = params.int_e_max
        self.int_e_min      = params.int_e_min
        self.u_max          = params.u_max
        self.u_min          = params.u_min
        self.du_max         = params.du_max
        self.du_min         = params.du_min

    # Below are helper functions that might be helpful in debugging.
    def get_refs(self) -> Tuple[float, float]:
        return (self.x_ref, self.u_ref)

    def get_errors(self) -> Tuple[float, float, float]:
        return (self.e, self.de, self.ei)

    # Below are clipping functions to enforce hard constraints.
    def _saturate_abs_high(self, u: float) -> float:
        return np.minimum(u, self.u_max)

    def _saturate_abs_low(self, u: float) -> float:
        return np.maximum(u, self.u_min)

    def _saturate_rel_high(self, du: float) -> float:
        return np.minimum(du, self.du_max)

    def _saturate_rel_low(self, du: float) -> float:
        return np.maximum(du, self.du_min)
Markdown

Part 1: Implementing and tuning a PID lane follower

TODO: Introductions. Example:

In this implementation, we follow a simple idea to control the speed and steering control separately by designing two separate PID controllers.

Specifically, the speed control PID tries to follow a reference speed, while the steering control PID tries to minimize the lateral deviation from the reference path.

Note that this PID controller only follows the center line of the track at a constant velocity. Later, we will implement a better controller that can follow a race line.

In [ ]
from mpclab_controllers.abstract_controller import AbstractController
from mpclab_common.pytypes import VehicleState

class PIDLaneFollower(AbstractController):
    '''
    Class for PID throttle and steering control of a vehicle
    Incorporates separate PID controllers for maintaining a constant speed and a constant lane offset

    target speed: v_ref
    target lane offset_ x_ref
    '''
    def __init__(self, dt: float,
                steer_pid_params: PIDParams = None,
                speed_pid_params: PIDParams = None):
        if steer_pid_params is None:
            steer_pid_params = PIDParams()
            steer_pid_params.dt = dt
            steer_pid_params.default_steer_params()
        if speed_pid_params is None:
            speed_pid_params = PIDParams()
            speed_pid_params.dt = dt
            speed_pid_params.default_speed_params()  # these may use dt so it is updated first

        self.dt = dt
        steer_pid_params.dt = dt
        speed_pid_params.dt = dt

        self.steer_pid_params = steer_pid_params
        self.speed_pid_params = speed_pid_params

        self.steer_pid = PID(self.steer_pid_params)
        self.speed_pid = PID(self.speed_pid_params)

        self.lat_ref = steer_pid_params.x_ref
        self.steer_pid.set_x_ref(0)  # For simplicity, we will just follow the center line in this problem. DO NOT MODIFY!

        self.requires_env_state = False
        return

    def reset(self):
        # Reinstantiate the two PID controllers.
        self.steer_pid = PID(self.steer_pid_params)
        self.speed_pid = PID(self.speed_pid_params)

    def initialize(self, **args):
        return

    def solve(self, *args, **kwargs):
        raise NotImplementedError

    def step(self, vehicle_state: VehicleState, env_state = None):
        v = vehicle_state.v.v_long

        vehicle_state.u.u_a, _ = self.speed_pid.solve(v)

        # Weighting factor: alpha*x_trans + beta*psi_diff
        alpha = 5.0
        beta = 1.0
        vehicle_state.u.u_steer, _ = self.steer_pid.solve(alpha*(vehicle_state.p.x_tran - self.lat_ref) + beta*vehicle_state.p.e_psi)
        return np.array([vehicle_state.u.u_a, vehicle_state.u.u_steer])

    def get_prediction(self):
        return None

    def get_safe_set(self):
        return None
Markdown

Create an instance of your PID controller with the parameters

Initialize/Reset your controller. If you decide to implement your own controller, it must have a step function with the following method signature:

def step(state: VehicleState) -> np.ndarray:
    ...

Also, make sure your controller also modify the action fields (state.u.u_a and state.u.u_steer) in place, so that it will work in the hardware experiments later.

In [ ]
#@markdown ## Set PID parameters
#@markdown ### Steering PID Parameters
Kp_steer = 0.85 #@param {type:"slider", min:0.0, max:2.0, step:0.05}
Ki_steer = 0  #@param {type:"slider", min:0.0, max:2.0, step:0.05}
Kd_steer = 0.05  #@param {type:"slider", min:0.0, max:2.0, step:0.05}

#@markdown ### Speed PID Parameters
Kp_speed = 0.3 #@param {type:"slider", min:0.0, max:2.0, step:0.05}
Ki_speed = 0  #@param {type:"slider", min:0.0, max:2.0, step:0.05}
Kd_speed = 0  #@param {type:"slider", min:0.0, max:2.0, step:0.05}
reference_speed = 1.0  #@param

# Some other global parameters. Don't modify them!
seed = 42
dt = 0.1
In [ ]
pid_steer_params = PIDParams(dt=dt,
                             Kp=Kp_steer, # Use the parameters from the sliders above.
                             Ki=Ki_steer,
                             Kd=Kd_steer,
                             u_max=0.436,
                             u_min=-0.436,
                             du_max=4.5,
                             du_min=-4.5,
                             x_ref=0.0,  # Offset from the center line. Set to 0 to follow the center line.
                             )

pid_speed_params = PIDParams(dt=dt,
                             Kp=Kp_speed,
                             Ki=Ki_speed,
                             Kd=Kd_speed,
                             u_max=2.0,
                             u_min=-2.0,
                             du_max=20.0,
                             du_min=-20.0,
                             x_ref=reference_speed,  # Reference speed.
                             )
pid_controller = PIDLaneFollower(dt, pid_steer_params, pid_speed_params)
Markdown

Simulate the controller

The environment is implemented following the standard definition of OpenAI gym.

Explanation of the parameters:

  • truncated indicates whether it is at a terminal state (collision with the boundary, going too slow (slower than 0.25 m/s), going in the wrong way, or finished the requested number of laps)
  • terminated indicates whether the car just finished a lap.
  • info is a dictionary that contains the ground truth vehicle_state and other helpful debugging information.
In [ ]
# Create an instance of the simulator.
env = gym.make('barc-v0',
               track_name='L_track_barc',
               do_render=True,
               max_n_laps=2,  # Feel free to modify this.
               in_colab=True,
               )
env.unwrapped.bind_controller(pid_controller)

# Reset the car to the starting line, with an initial velocity of 0.5 m/s.
_, info = env.reset(seed=seed, options={'spawning': 'fixed'})

truncated = False # Flag indicating whether the car is at a terminal state.

# Reset the PID controller (clear the errors).
pid_controller.reset()

lap_time = []  # Keep track of the lap times.

while not truncated:
    action = pid_controller.step(vehicle_state=info['vehicle_state']) # Call the controller to get the steering and speed commands.
    _, _, terminated, truncated, info = env.step(action) # Apply the action on the car and get the updated state.

    if terminated:
        lap_time.append(info['lap_time'])  # Keep track of the time it took to finish a lap.

logger.info(f"Average lap time: {np.mean(lap_time):.1f} s. Std: {np.std(lap_time):.1f} s.")
logger.info("Rollout truncated.")
2025-04-22 22:16:25.310 | DEBUG    | gym_carla.envs.barc.barc_env:reset:181 - Respawning at fixed location.
2025-04-22 22:16:33.717 | INFO     | gym_carla.envs.barc.barc_env:step:258 - Lap 0 finished in 19.8 s. avg_v = 0.8974, max_v = 0.9836, min_v = 0.5000
2025-04-22 22:16:40.149 | INFO     | gym_carla.envs.barc.barc_env:step:258 - Lap 1 finished in 18.3 s. avg_v = 0.9733, max_v = 0.9895, min_v = 0.9358
2025-04-22 22:16:40.181 | INFO     | __main__:&lt;cell line: 0&gt;:27 - Average lap time: 19.1 s. Std: 0.8 s.
2025-04-22 22:16:40.185 | INFO     | __main__:&lt;cell line: 0&gt;:28 - Rollout truncated.
Markdown

Playback of the race

This cell generates a matplotlib figure of the trajectory and statistics of the race that is just simulated. Use this to debug and tune your controller!

In [ ]
env.unwrapped.show_debug_plot()
Notebook output image
Markdown

Animation (Optional)

Render an animation of the race.

Note that this may take a long time (about 4 frames per second). (FYI the simulation runs at 10 frames per second)

The playback speed is 2x compared to real time.

In [ ]
render_animation = False  #@param {type:"boolean"}
In [ ]
if render_animation:
    from IPython.display import HTML
    animation = env.unwrapped.visualizer.get_animation()
    print(f"Please wait for the animation to be rendered. Estimated total wait time: {len(env.unwrapped.v_buffer) * 0.25:.1f} s. ")
    display(HTML(animation.to_html5_video()))
Markdown

Compare with hardware data (Optional)

Warning: Upload the csv file converted from the ROS bag that is generated on your hardware test first!

Replace TODO with your group name. Your csv file should have the same name.

In [ ]
compare_with_hardware_data = False  #@param {type:"boolean"}
group_name = "group_1" #@param {type:"string"}
In [ ]
if compare_with_hardware_data:
    import pandas as pd
    from google.colab import files
    import os

    while not os.path.exists(f'{group_name}.csv'):
        print(f"Data file {group_name}.csv not found. Please upload it here.")
        uploaded = files.upload()
    df = pd.read_csv(f'{group_name}.csv', delimiter=',')
    # df = pd.read_csv(f'{group_name}.csv', delimiter=',')

    fig, axes = plt.subplots(2, 2, figsize=(9, 9))
    ((ax_traj, ax_v), (ax_u_a, ax_u_steer)) = axes
    start_idx = np.where((df['u_a'] != 0) & (df['u_steer'] != 0) & (df['v_long'] >= 0.5))[0][0]
    t = df['t'][start_idx:]
    t = (t - t.min()) / 1e9

    env.unwrapped.get_track().plot_map(ax=ax_traj)
    ax_traj.plot(df['x'][start_idx:], df['y'][start_idx:], label='hardware')
    ax_traj.set_title("Trajectory Playback")
    ax_traj.set_xlabel('x(m)')
    ax_traj.set_ylabel('y(m)')

    ax_v.plot(t, df['v_long'][start_idx:], label='hardware')
    ax_v.set_title("Velocity playback")
    ax_v.set_xlabel('t(s)')
    ax_v.set_ylabel('v(m/s)')

    ax_u_a.plot(t, df['u_a'][start_idx:], label='hardware')
    ax_u_a.set_title("Acceleration input playback")
    ax_u_a.set_xlabel('t(s)')
    ax_u_a.set_ylabel('$u_a$')

    ax_u_steer.plot(t, df['u_steer'][start_idx:], label='hardware')
    ax_u_steer.set_title('Steering input playback')
    ax_u_steer.set_xlabel('t(s)')
    ax_u_steer.set_ylabel('$u_{steer}$')

    env.unwrapped.show_debug_plot(axes)

    for ax in axes.flatten():
        ax.legend()

    plt.show()
Markdown

Part 2: Raceline Tracking experiment

**TODO:**Introduction.

Example: Now we redesign the PID controller to track any reference, so that it can follow the raceline we derived and go in high speed.

In [ ]
%%writefile my_script.py
from mpclab_controllers.abstract_controller import AbstractController
from mpclab_common.pytypes import VehicleState

class PIDRacelineFollower(AbstractController):
    '''
    Class for PID throttle and steering control of a vehicle
    Incorporates separate PID controllers for maintaining a constant speed and a constant lane offset

    target speed: v_ref
    target lane offset_ x_ref
    '''
    def __init__(self, dt: float,
                steer_pid_params: PIDParams = None,
                speed_pid_params: PIDParams = None):
        if steer_pid_params is None:
            steer_pid_params = PIDParams()
            steer_pid_params.dt = dt
            steer_pid_params.default_steer_params()
        if speed_pid_params is None:
            speed_pid_params = PIDParams()
            speed_pid_params.dt = dt
            speed_pid_params.default_speed_params()  # these may use dt so it is updated first

        self.dt = dt
        steer_pid_params.dt = dt
        speed_pid_params.dt = dt

        self.steer_pid_params = steer_pid_params
        self.speed_pid_params = speed_pid_params

        self.steer_pid_params.x_ref = 0  # TODO: This is temporary. Properly implement this.
        self.speed_pid_params.x_ref = 0  # TODO: This is temporary. Properly implement this.

        self.steer_pid = PID(self.steer_pid_params)
        self.speed_pid = PID(self.speed_pid_params)

        self.lat_ref = steer_pid_params.x_ref

        self.requires_env_state = False
        return

    def reset(self):
        # Reinstantiate the two PID controllers.
        self.steer_pid = PID(self.steer_pid_params)
        self.speed_pid = PID(self.speed_pid_params)

    def initialize(self, **args):
        return

    def solve(self, *args, **kwargs):
        raise NotImplementedError

    def step(self, vehicle_state: VehicleState, reference=None):
        # """
        # Reference solution (needs more test to get it to work):

        v_ref, x_tran_ref, psi_ref = reference['x0'], reference['x5'], reference['x3']
        u0_ref, u1_ref = reference['u0'], reference['u1']

        v = vehicle_state.v.v_long
        vehicle_state.u.u_a, _ = self.speed_pid.solve(v - v_ref)

        # Weighting factor: alpha*x_trans + beta*psi_diff
        alpha = 5.0
        beta = 1.0
        vehicle_state.u.u_steer, _ = self.steer_pid.solve(alpha*(vehicle_state.p.x_tran - x_tran_ref) + beta* (vehicle_state.p.e_psi - psi_ref))
        return np.array([vehicle_state.u.u_a, vehicle_state.u.u_steer])
        # """
        raise NotImplementedError
        # ============YOUR CODE HERE===============
        vehicle_state.u.u_a = TODO
        vehicle_state.u.u_steer = TODO
        # ============END YOUR CODE================
        return np.array([vehicle_state.u.u_a, vehicle_state.u_steer])

    def get_prediction(self):
        return None

    def get_safe_set(self):
        return None
Overwriting my_script.py
In [ ]
from google.colab import files
files.download('my_script.py')
&lt;IPython.core.display.Javascript object&gt;
&lt;IPython.core.display.Javascript object&gt;
In [ ]
import numpy as np
import os
from google.colab import files


class Raceline:
    def __init__(self, raceline_file='raceline.npz'):
        while not os.path.exists(raceline_file):
            print(f"Raceline data file {raceline_file} is not found. Please upload it here. ")
            uploaded = files.upload()
        data = np.load(raceline_file)
        self.s, self.ey, self.epsi, self.vx, self.vy, self.epsi_dot, self.t, self.u0, self.u1 = map(np.array, (data['s'], data['e_y'], data['e_psi'], data['v_long'], data['v_tran'], data['psidot'], data['t'], data['u_a'], data['u_s']))

    def get_reference(self, _s, speed_scaling=1.):
        # interp = lambda yp: np.interp(_s, self.s, yp)
        # ey_ref, epsi_ref, vx_ref, vy_ref, epsi_dot_ref, t_ref, u0_ref, u1_ref = map(interp, (self.ey, self.epsi, self.vx, self.vy, self.epsi_dot, self.t, self.u0, self.u1))

        ey_ref = np.interp(_s, self.s, self.ey)
        epsi_ref = np.interp(_s, self.s, self.epsi)
        vx_ref = np.interp(_s, self.s, self.vx) * speed_scaling  # TODO: This is just a trick to scale down the speed to make the raceline easier to track. PROPERLY IMPLEMENT THIS!
        vy_ref = np.interp(_s, self.s, self.vy)
        epsi_dot_ref = np.interp(_s, self.s, self.epsi_dot)
        t_ref = np.interp(_s, self.s, self.t)
        u0_ref = np.interp(_s, self.s, self.u0)
        u1_ref = np.interp(_s, self.s, self.u1)
        return {
            's': _s,
            'x0': vx_ref,
            'x1': vy_ref,
            'x2': epsi_dot_ref,
            'x3': epsi_ref,
            'x4': t_ref,
            'x5': ey_ref,
            'u0': u0_ref,
            'u1': u1_ref,
        }

    def plot_raceline(self, ax):
        svec = self.s
        Psi = self.epsi[0]
        X, Y = [0], [self.ey[0]]
        for j in range(1,len(svec)):
            sj = svec[j]
            deltaT = self.t[j] - self.t[j-1]
            Psi = Psi + deltaT * self.epsi_dot[j]
            X.append(X[j-1] + deltaT * (self.vx[j] * np.cos(Psi) - self.vy[j] * np.sin(Psi)))
            Y.append(Y[j-1] + deltaT * (self.vx[j] * np.sin(Psi) + self.vy[j] * np.cos(Psi)))
        ax.plot(X, Y, c='r', ls='--', label='raceline')

Raceline().get_reference(1)  # TODO: Remove this debugging command.
Raceline data file raceline.npz is not found. Please upload it here. 
Upload widget is only available when the cell has been executed in the current browser session. Please rerun this cell to enable.
Raceline data file raceline.npz is not found. Please upload it here. 
Upload widget is only available when the cell has been executed in the current browser session. Please rerun this cell to enable.
KeyboardInterrupt:
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
&lt;ipython-input-21-b526ae40bded&gt; in &lt;cell line: 0&gt;()
     48         ax.plot(X, Y, c=&#39;r&#39;, ls=&#39;--&#39;, label=&#39;raceline&#39;)
     49 
---&gt; 50 Raceline().get_reference(1)  # TODO: Remove this debugging command.

&lt;ipython-input-21-b526ae40bded&gt; in __init__(self, raceline_file)
      8         while not os.path.exists(raceline_file):
      9             print(f&#34;Raceline data file {raceline_file} is not found. Please upload it here. &#34;)
---&gt; 10             uploaded = files.upload()
     11         data = np.load(raceline_file)
     12         self.s, self.ey, self.epsi, self.vx, self.vy, self.epsi_dot, self.t, self.u0, self.u1 = map(np.array, (data[&#39;s&#39;], data[&#39;e_y&#39;], data[&#39;e_psi&#39;], data[&#39;v_long&#39;], data[&#39;v_tran&#39;], data[&#39;psidot&#39;], data[&#39;t&#39;], data[&#39;u_a&#39;], data[&#39;u_s&#39;]))

/usr/local/lib/python3.11/dist-packages/google/colab/files.py in upload(target_dir)
     70   &#34;&#34;&#34;
     71 
---&gt; 72   uploaded_files = _upload_files(multiple=True)
     73   # Mapping from original filename to filename as saved locally.
     74   local_filenames = dict()

/usr/local/lib/python3.11/dist-packages/google/colab/files.py in _upload_files(multiple)
    162 
    163   # First result is always an indication that the file picker has completed.
--&gt; 164   result = _output.eval_js(
    165       &#39;google.colab._files._uploadFiles(&#34;{input_id}&#34;, &#34;{output_id}&#34;)&#39;.format(
    166           input_id=input_id, output_id=output_id

/usr/local/lib/python3.11/dist-packages/google/colab/output/_js.py in eval_js(script, ignore_result, timeout_sec)
     38   if ignore_result:
     39     return
---&gt; 40   return _message.read_reply_from_input(request_id, timeout_sec)
     41 
     42 

/usr/local/lib/python3.11/dist-packages/google/colab/_message.py in read_reply_from_input(message_id, timeout_sec)
     94     reply = _read_next_input_message()
     95     if reply == _NOT_READY or not isinstance(reply, dict):
---&gt; 96       time.sleep(0.025)
     97       continue
     98     if (

KeyboardInterrupt: 
In [ ]
%%writefile config.py
#@markdown ## Set PID parameters
#@markdown ### Steering PID Parameters
Kp_steer = 0.25 #@param {type:"slider", min:0.0, max:2.0, step:0.05}
Ki_steer = 0.1  #@param {type:"slider", min:0.0, max:2.0, step:0.05}
Kd_steer = 0.05  #@param {type:"slider", min:0.0, max:2.0, step:0.05}

#@markdown ### Speed PID Parameters
Kp_speed = 0.3 #@param {type:"slider", min:0.0, max:2.0, step:0.05}
Ki_speed = 0  #@param {type:"slider", min:0.0, max:2.0, step:0.05}
Kd_speed = 0  #@param {type:"slider", min:0.0, max:2.0, step:0.05}

# Some other global parameters. Don't modify them!
seed = 42
dt = 0.1
In [ ]
pid_steer_params_raceline_follower = PIDParams(dt=dt,
                             Kp=Kp_steer, # Use the parameters from the sliders above.
                             Ki=Ki_steer,
                             Kd=Kd_steer,
                             u_max=0.436,
                             u_min=-0.436,
                             du_max=4.5,
                             du_min=-4.5,
                             x_ref=0.0,  # Offset from the center line. Set to 0 to follow the center line.
                             )

pid_speed_params_raceline_follower = PIDParams(dt=dt,
                             Kp=Kp_speed,
                             Ki=Ki_speed,
                             Kd=Kd_speed,
                             u_max=2.0,
                             u_min=-2.0,
                             du_max=20.0,
                             du_min=-20.0,
                             x_ref=reference_speed,  # Reference speed.
                             )
pid_controller = PIDLaneFollower(dt, pid_steer_params, pid_speed_params)
In [ ]
# Create an instance of the simulator.
path_follower = PIDRacelineFollower(dt=dt, steer_pid_params=pid_steer_params_raceline_follower, speed_pid_params=pid_speed_params_raceline_follower)
raceline = Raceline()

env = gym.make('barc-v0',
               track_name='L_track_barc',
               do_render=True,
               max_n_laps=50,  # Feel free to modify this.
               in_colab=True,
               )
env.unwrapped.bind_controller(path_follower)

# Reset the car to the starting line, with an initial velocity of 0.5 m/s.
_, info = env.reset(seed=seed, options={'spawning': 'fixed'})

truncated = False # Flag indicating whether the car is at a terminal state.

# Reset the PID controller (clear the errors).
path_follower.reset()

lap_time = []  # Keep track of the lap times.

while not truncated:
    reference = raceline.get_reference(info['vehicle_state'].p.s, speed_scaling=0.7)
    action = path_follower.step(vehicle_state=info['vehicle_state'], reference=reference) # Call the controller to get the steering and speed commands.
    _, _, terminated, truncated, info = env.step(action) # Apply the action on the car and get the updated state.
    if terminated:
        lap_time.append(info['lap_time'])  # Keep track of the time it took to finish a lap.

logger.info(f"Average lap time: {np.mean(lap_time):.1f} s. Std: {np.std(lap_time):.1f} s.")
logger.info("Rollout truncated.")
In [ ]
fig, axes = plt.subplots(2, 2, figsize=(9, 9))
env.unwrapped.show_debug_plot(axes)
raceline.plot_raceline(ax=axes[0][0])
for ax in axes.flatten():
    ax.legend()
plt.show()
Markdown

Compute a new raceline (Optional)

In [ ]
import sys
IN_COLAB = 'google.colab' in sys.modules
if IN_COLAB:
#   !pip install -q pyomo
  !apt-get install -y -qq glpk-utils
  !wget -N -q "https://matematica.unipv.it/gualandi/solvers/ipopt-linux64.zip"
  !unzip -o -q ipopt-linux64
Selecting previously unselected package libsuitesparseconfig5:amd64.
(Reading database ... 126333 files and directories currently installed.)
Preparing to unpack .../libsuitesparseconfig5_1%3a5.10.1+dfsg-4build1_amd64.deb ...
Unpacking libsuitesparseconfig5:amd64 (1:5.10.1+dfsg-4build1) ...
Selecting previously unselected package libamd2:amd64.
Preparing to unpack .../libamd2_1%3a5.10.1+dfsg-4build1_amd64.deb ...
Unpacking libamd2:amd64 (1:5.10.1+dfsg-4build1) ...
Selecting previously unselected package libcolamd2:amd64.
Preparing to unpack .../libcolamd2_1%3a5.10.1+dfsg-4build1_amd64.deb ...
Unpacking libcolamd2:amd64 (1:5.10.1+dfsg-4build1) ...
Selecting previously unselected package libglpk40:amd64.
Preparing to unpack .../libglpk40_5.0-1_amd64.deb ...
Unpacking libglpk40:amd64 (5.0-1) ...
Selecting previously unselected package glpk-utils.
Preparing to unpack .../glpk-utils_5.0-1_amd64.deb ...
Unpacking glpk-utils (5.0-1) ...
Setting up libsuitesparseconfig5:amd64 (1:5.10.1+dfsg-4build1) ...
Setting up libamd2:amd64 (1:5.10.1+dfsg-4build1) ...
Setting up libcolamd2:amd64 (1:5.10.1+dfsg-4build1) ...
Setting up libglpk40:amd64 (5.0-1) ...
Setting up glpk-utils (5.0-1) ...
Processing triggers for man-db (2.10.2-1) ...
Processing triggers for libc-bin (2.35-0ubuntu3.8) ...
/sbin/ldconfig.real: /usr/local/lib/libtcm.so.1 is not a symbolic link

/sbin/ldconfig.real: /usr/local/lib/libtbbbind.so.3 is not a symbolic link

/sbin/ldconfig.real: /usr/local/lib/libtcm_debug.so.1 is not a symbolic link

/sbin/ldconfig.real: /usr/local/lib/libur_adapter_level_zero.so.0 is not a symbolic link

/sbin/ldconfig.real: /usr/local/lib/libumf.so.0 is not a symbolic link

/sbin/ldconfig.real: /usr/local/lib/libtbbbind_2_0.so.3 is not a symbolic link

/sbin/ldconfig.real: /usr/local/lib/libur_loader.so.0 is not a symbolic link

/sbin/ldconfig.real: /usr/local/lib/libtbbbind_2_5.so.3 is not a symbolic link

/sbin/ldconfig.real: /usr/local/lib/libhwloc.so.15 is not a symbolic link

/sbin/ldconfig.real: /usr/local/lib/libtbbmalloc_proxy.so.2 is not a symbolic link

/sbin/ldconfig.real: /usr/local/lib/libur_adapter_opencl.so.0 is not a symbolic link

/sbin/ldconfig.real: /usr/local/lib/libtbbmalloc.so.2 is not a symbolic link

/sbin/ldconfig.real: /usr/local/lib/libtbb.so.12 is not a symbolic link

In [ ]
# Original code at: https://github.com/MPC-Berkeley/barc_gym/blob/main/mpclab_common/mpclab_common/tracks/solve_raceline.py

import numpy as np
from pyomo.environ import *
from pyomo.dae import *
import pandas as pd

from mpclab_common.pytypes import BodyAngularVelocity, BodyLinearVelocity, ParametricPose, VehicleState, VehicleActuation
# from mpclab_common.track import get_track

def compute_raceline(track, vehicle_config, vehicle_constraints,
                     obj_num=0,
                     raceline_to_avoid=None,
                     track_tightening=0,
                     constant_width=False):

    #SYSTEM STATES:  vx=x[0],  vy=x[1], wz=x[2] ,e_psi=x[3], t=x[4], e_y=x[5]
    #SYSTEM INPUTS:  ax[m/s^2]=u0, steering(rad)=u1
    #INDEPENDENT VARIABLE IS s (space)

    lf = vehicle_config.wheel_dist_front
    lr = vehicle_config.wheel_dist_rear

    mass = vehicle_config.mass
    Iz = vehicle_config.yaw_inertia

    tire_model = vehicle_config.tire_model

    mu = vehicle_config.wheel_friction
    Br = vehicle_config.pacejka_b_rear
    Bf = vehicle_config.pacejka_b_front
    Cr = vehicle_config.pacejka_c_rear
    Cf = vehicle_config.pacejka_c_front
    Dr = vehicle_config.pacejka_d_rear
    Df = vehicle_config.pacejka_d_front

    xu_ub = vehicle_constraints['xu_ub']
    xu_lb = vehicle_constraints['xu_lb']
    du_ub = vehicle_constraints['du_ub']
    du_lb = vehicle_constraints['du_lb']

    v_long_max = xu_ub.v.v_long
    v_long_min = xu_lb.v.v_long
    v_tran_max = xu_ub.v.v_tran
    v_tran_min = xu_lb.v.v_tran
    psidot_max = xu_ub.w.w_psi
    psidot_min = xu_lb.w.w_psi
    e_psi_max = xu_ub.p.e_psi
    e_psi_min = xu_lb.p.e_psi
    if constant_width:
        e_y_max = track.half_width - track_tightening
        e_y_min = -(track.half_width - track_tightening)
    else:
        # e_y_max = xu_ub.p.x_tran
        # e_y_min = xu_lb.p.x_tran
        e_y_max = 1e9
        e_y_min = -1e9

    a_max = xu_ub.u.u_a
    a_min = xu_lb.u.u_a
    steer_max = xu_ub.u.u_steer
    steer_min = xu_lb.u.u_steer

    a_rate_max = du_ub.u_a
    a_rate_min = du_lb.u_a
    steer_rate_max = du_ub.u_steer
    steer_rate_min = du_lb.u_steer

    print('=================== Raceline computation ===================')
    if track.circuit:
        print('Track is a circuit')
    else:
        print('Track is not a circuit')
    if constant_width:
        print('Using constant width')
    else:
        print('Using varying width')
    # print('Track name: %s' % track_name)
    # print('     - length: %g m' % track.track_length)
    # print('     - avg width: %g m' % (track.half_width*2))
    print('Vehicle configuration:')
    print('     - center of mass to front axle: %g m' % lf)
    print('     - center of mass to rear axle: %g m' % lr)
    print('     - mass: %g kg' % mass)
    print('     - yaw inertia: %g kg m^2' % Iz)
    print('     - tire model: %s' % tire_model)
    print('     - tire friction coefficient: %g' % mu)
    print('     - Front tires:')
    print('         - B: %g' % Bf)
    print('         - C: %g' % Cf)
    print('         - D: %g' % Df)
    print('     - Rear tires:')
    print('         - B: %g' % Br)
    print('         - C: %g' % Cr)
    print('         - D: %g' % Dr)
    print('Vehicle input constraints:')
    print('     - max acceleration: %g m/s^2' % a_max)
    print('     - min acceleration: %g m/s^2' % a_min)
    print('     - max steering angle: %g rad' % steer_max)
    print('     - min steering angle: %g rad' % steer_min)
    print('Vehicle state constraints:')
    print('     - max longitudinal velocity: %g m/s' % v_long_max)
    print('     - min longitudinal velocity: %g m/s' % v_long_min)
    print('     - max lateral velocity: %g m/s' % v_tran_max)
    print('     - min lateral velocity: %g m/s' % v_tran_min)
    print('     - max yaw rate: %g rad/s' % psidot_max)
    print('     - min yaw rate: %g rad/s' % psidot_min)
    print('============================================================')

    m = ConcreteModel()
    m.sf = Param(initialize=track.track_length)
    m.s = ContinuousSet(bounds=(0, m.sf))

    m.alpha_f   = Var(m.s, initialize=0)
    m.alpha_r   = Var(m.s, initialize=0)
    m.Fyf       = Var(m.s,bounds=(-mass*9.8, mass*9.8), initialize=0)
    m.Fyr       = Var(m.s,bounds=(-mass*9.8, mass*9.8), initialize=0)
    m.x0        = Var(m.s, bounds=(v_long_min, v_long_max), initialize=v_long_min)
    m.x1        = Var(m.s, bounds=(v_tran_min, v_tran_max), initialize=0)
    m.x2        = Var(m.s, bounds=(psidot_min, psidot_max), initialize=0)
    m.x3        = Var(m.s, bounds=(e_psi_min, e_psi_max))
    m.x4        = Var(m.s, bounds=(0, 20000), initialize=0)
    m.x5        = Var(m.s, bounds=(e_y_min, e_y_max), initialize=0)
    m.u0        = Var(m.s, bounds=(a_min, a_max), initialize=0)
    m.u1        = Var(m.s, bounds=(steer_min, steer_max), initialize=0)
    m.du0       = Var(m.s, bounds=(a_rate_min, a_rate_max), initialize=0)
    # m.du0       = Var(m.s, initialize=0)
    m.du1       = Var(m.s, bounds=(steer_rate_min, steer_rate_max), initialize=0)
    # m.du1       = Var(m.s, initialize=0)

    m.dx0ds = DerivativeVar(m.x0, wrt=m.s)
    m.dx1ds = DerivativeVar(m.x1, wrt=m.s)
    m.dx2ds = DerivativeVar(m.x2, wrt=m.s)
    m.dx3ds = DerivativeVar(m.x3, wrt=m.s)
    m.dx4ds = DerivativeVar(m.x4, wrt=m.s)
    m.dx5ds = DerivativeVar(m.x5, wrt=m.s)
    m.du0ds = DerivativeVar(m.u0, wrt=m.s)
    m.du1ds = DerivativeVar(m.u1, wrt=m.s)

    # to avoid divide by 0
    eps=0.000001

    #Objective function
    if obj_num == 0:
        m.obj = Objective(expr=m.x4[m.sf], sense=minimize)
    elif obj_num == 1:
        m.obj = Objective(expr=m.x4[m.sf] + 0.1*sum(m.du1ds[i] for i in m.s), sense=minimize)
    elif obj_num == 2:
        m.obj = Objective(expr=m.x4[m.sf] + 0.01*sum(m.du1ds[i] for i in m.s), sense=minimize)
    elif obj_num == 3:
        m.obj = Objective(expr=m.x4[m.sf] + 0.001*sum(m.du1ds[i] for i in m.s), sense=minimize)
    elif obj_num == 4:
        m.obj = Objective(expr=m.x4[m.sf] + 0.005*sum(m.du1ds[i] for i in m.s), sense=minimize)
    elif obj_num == 5:
        # Penalized lap time, inputs, and input rates
        m.obj = Objective(expr=m.x4[m.sf] + 0.01*sum(m.du0ds[i]**2 for i in m.s) + 0.01*sum(m.du1ds[i]**2 for i in m.s), sense=minimize)
        # m.obj = Objective(expr=m.x4[m.sf] + 0.01*sum(m.du0[i]**2 for i in m.s) + 0.01*sum(m.du1[i]**2 for i in m.s), sense=minimize)
        # m.obj = Objective(expr=m.x4[m.sf] + 0.001*sum(m.u0[i]**2 for i in m.s) + 0.001*sum(m.u1[i]**2 for i in m.s) + 0.01*sum(m.du0ds[i]**2 for i in m.s) + 0.01*sum(m.du1ds[i]**2 for i in m.s), sense=minimize)

    #sideslip and lateral force
    def _alphafc(m, s):
        return m.alpha_f[s] == m.u1[s] - atan((m.x1[s] + lf * m.x2[s])/ (m.x0[s]))
    m.c4 = Constraint(m.s, rule=_alphafc)
    def _alpharc(m, s):
        return m.alpha_r[s] == -atan((m.x1[s] - lr * m.x2[s])/ (m.x0[s]))
    m.c3 = Constraint(m.s, rule=_alpharc)

    if tire_model == 'linear':
        def _Fyfc(m, s):
            return m.Fyf[s] ==  Df * Cf * Bf * m.alpha_f[s]
        m.c2 = Constraint(m.s, rule=_Fyfc)
        def _Fyrc(m, s):
            return m.Fyr[s] ==  Dr * Cr * Br * m.alpha_r[s]
        m.c1 = Constraint(m.s, rule=_Fyrc)
    elif tire_model == 'pacejka':
        def _Fyfc(m, s):
            return m.Fyf[s] ==  Df * sin(Cf * atan(Bf * m.alpha_f[s]))
        m.c2 = Constraint(m.s, rule=_Fyfc)
        def _Fyrc(m, s):
            return m.Fyr[s] ==  Dr * sin(Cr * atan(Br * m.alpha_r[s]))
        m.c1 = Constraint(m.s, rule=_Fyrc)

    #Differential model definition
    def _x0dot(m, s):
        cur = track.get_curvature(s)
        return m.dx0ds[s] == (m.u0[s] - 1 / mass *  m.Fyf[s] * sin(m.u1[s]) + m.x2[s]*m.x1[s])*((1 - cur * m.x5[s]) /(eps+m.x0[s] * cos(m.x3[s]) - m.x1[s] * sin(m.x3[s])))
    m.x0dot = Constraint(m.s, rule=_x0dot)

    def _x1dot(m, s):
        cur = track.get_curvature(s)
        return m.dx1ds[s] == (1 / mass * (m.Fyf[s] * cos(m.u1[s]) + m.Fyr[s]) - m.x2[s] * m.x0[s])*((1 - cur * m.x5[s]) /(eps+m.x0[s] * cos(m.x3[s]) - m.x1[s] * sin(m.x3[s])))
    m.x1dot = Constraint(m.s, rule=_x1dot)

    def _x2dot(m, s):
        cur = track.get_curvature(s)
        return m.dx2ds[s] == (1 / Iz *(lf*m.Fyf[s] * cos(m.u1[s]) - lr * m.Fyr[s]) )*((1 - cur * m.x5[s]) /(eps+m.x0[s] * cos(m.x3[s]) - m.x1[s] * sin(m.x3[s])))
    m.x2dot = Constraint(m.s, rule=_x2dot)

    def _x3dot(m, s):
        cur = track.get_curvature(s)
        return m.dx3ds[s] == ( m.x2[s]*(1 - cur * m.x5[s])/(eps+m.x0[s] * cos(m.x3[s]) - m.x1[s] * sin(m.x3[s])) - cur)
    m.x3dot = Constraint(m.s, rule=_x3dot)

    def _x4dot(m, s):
        cur = track.get_curvature(s)
        return m.dx4ds[s] == ((1 - cur * m.x5[s]) /(eps+m.x0[s] * cos(m.x3[s]) - m.x1[s] * sin(m.x3[s])))
    m.x4dot = Constraint(m.s, rule=_x4dot)

    def _x5dot(m, s):
        cur = track.get_curvature(s)
        return m.dx5ds[s] == (m.x0[s] * sin(m.x3[s]) + m.x1[s] * cos(m.x3[s]))*((1 - cur * m.x5[s]) /(eps+m.x0[s] * cos(m.x3[s]) - m.x1[s] * sin(m.x3[s])))
    m.x5dot = Constraint(m.s, rule=_x5dot)

    def _u0dot(m, s):
        cur = track.get_curvature(s)
        return m.du0ds[s] == m.du0[s] * ((1 - cur * m.x5[s]) /(eps+m.x0[s] * cos(m.x3[s]) - m.x1[s] * sin(m.x3[s])))
    m.u0dot = Constraint(m.s, rule=_u0dot)

    def _u1dot(m, s):
        cur = track.get_curvature(s)
        return m.du1ds[s] == m.du1[s] * ((1 - cur * m.x5[s]) /(eps+m.x0[s] * cos(m.x3[s]) - m.x1[s] * sin(m.x3[s])))
    m.u1dot = Constraint(m.s, rule=_u1dot)

    # # rate constraints on acceleration
    # def _u0dotmax(m, s):
    #     cur = track.get_curvature(s)
    #     return m.du0ds[s] <= a_rate_max*((1 - cur * m.x5[s]) /(eps+m.x0[s] * cos(m.x3[s]) - m.x1[s] * sin(m.x3[s])))
    # m.i0dotub = Constraint(m.s, rule=_u0dotmax)
    # def _u0dotmin(m, s):
    #     cur = track.get_curvature(s)
    #     return m.du0ds[s] >= a_rate_min*((1 - cur * m.x5[s]) /(eps+m.x0[s] * cos(m.x3[s]) - m.x1[s] * sin(m.x3[s])))
    # m.i0dotlb = Constraint(m.s, rule=_u0dotmin)

    # # rate constraints on steering
    # def _u1dotmax(m, s):
    #     cur = track.get_curvature(s)
    #     return m.du1ds[s] <= steer_rate_max*((1 - cur * m.x5[s]) /(eps+m.x0[s] * cos(m.x3[s]) - m.x1[s] * sin(m.x3[s])))
    # m.i1dotub = Constraint(m.s, rule=_u1dotmax)
    # def _u1dotmin(m, s):
    #     cur = track.get_curvature(s)
    #     return m.du1ds[s] >= steer_rate_min*((1 - cur * m.x5[s]) /(eps+m.x0[s] * cos(m.x3[s]) - m.x1[s] * sin(m.x3[s])))
    # m.i1dotlb = Constraint(m.s, rule=_u1dotmin)

    # Variable track width
    if not constant_width:
        def _eymax(m, s):
            _w = float(track.left_width(s)) - track_tightening
            return m.x5[s] <= _w
        m.eymax = Constraint(m.s, rule=_eymax)
        def _eymin(m, s):
            _w = -(float(track.right_width(s)) - track_tightening)
            return m.x5[s] >= _w
        m.eymin = Constraint(m.s, rule=_eymin)

    if raceline_to_avoid is not None:
        # racing constraints: t2(s) + ey2(s) >= t1(s) + ey1(s) + buffer
        buffer = 0.5
        def _raceConstraint(m,s):
            m1x4, m1x5 = getValues(raceline_to_avoid, s)
            #return m.x4[s] + m.x5[s] >= m1x4 + m1x5 + buffer
            #return m.x5[s] <= m1x5 - 0.1
            return m.x5[s]+m1x4 <= m1x5 + m.x4[s] - 0.1

        m.rc = Constraint(m.s, rule=_raceConstraint)

    # inital and terminal conditions
    def _init(m):
        if track.circuit:
            yield m.x0[0] == m.x0[track.track_length]
            yield m.x1[0] == m.x1[track.track_length]
            yield m.x2[0] == m.x2[track.track_length]
            yield m.x3[0] == m.x3[track.track_length]
            yield m.x5[0] == m.x5[track.track_length]
        yield m.x4[0] == 0

    m.init_conditions = ConstraintList(rule=_init)

    # Discretize model using radau or finite difference collocation
    TransformationFactory('dae.collocation').apply_to(m, nfe=200, ncp=10, scheme='LAGRANGE-LEGENDRE') #STANDARD METHOD
    # TransformationFactory('dae.collocation').apply_to(m, nfe=500, ncp=50, scheme='LAGRANGE-LEGENDRE'  )
    # TransformationFactory('dae.collocation').apply_to(m, nfe=200, ncp=10, scheme='LAGRANGE-RADAU'  )
    #TransformationFactory('dae.collocation').apply_to(m, nfe=100, ncp=10, scheme='LAGRANGE-RADAU')
    # TransformationFactory('dae.finite_difference').apply_to(m, nfe=1000)
    # TransformationFactory('dae.finite_difference').apply_to(m, nfe=3000)
    # TransformationFactory('dae.finite_difference').apply_to(m, nfe=10000)

    # Solve algebraic model
    solver = SolverFactory('ipopt')
    # Solver options
    solver.options['max_iter'] = 1000
    # solver.options['linear_solver'] = 'ma27'
    # solver.options['mu_strategy'] = 'adaptive'
    #solver.options = {'tol': 1e-6,
    #                  'mu_init': 1e-8,
    #                  'bound_push': 1e-8,
    #                 'halt_on_ampl_error': 'yes'}

    solver.options = {'tol': 1e-2,
                      'mu_init': 1e-6}
    results = solver.solve(m, tee=True)

    # Evaluate solution at discretization points
    s_vec = list(sorted(m.s.data()))

    raceline = {'t': np.zeros(len(s_vec)),
                'x' : np.zeros(len(s_vec)),
                'y' : np.zeros(len(s_vec)),
                'psi' : np.zeros(len(s_vec)),
                's' : np.zeros(len(s_vec)),
                'e_y' : np.zeros(len(s_vec)),
                'e_psi' : np.zeros(len(s_vec)),
                'v_long' : np.zeros(len(s_vec)),
                'v_tran' : np.zeros(len(s_vec)),
                'psidot' : np.zeros(len(s_vec)),
                'u_a': np.zeros(len(s_vec)),
                'u_s': np.zeros(len(s_vec))}

    for j in range(len(s_vec)):
        s = s_vec[j]

        local_pos = (s, value(m.x5[s]), 0)
        (x, y, psi) = track.local_to_global(local_pos)

        raceline['t'][j]        = value(m.x4[s])
        raceline['v_long'][j]   = value(m.x0[s])
        raceline['v_tran'][j]   = value(m.x1[s])
        raceline['psidot'][j]   = value(m.x2[s])
        raceline['s'][j]        = s
        raceline['e_y'][j]      = value(m.x5[s])
        raceline['e_psi'][j]    = value(m.x3[s])
        raceline['x'][j]        = x
        raceline['y'][j]        = y
        raceline['psi'][j]      = psi
        raceline['u_a'][j]      = value(m.u0[s])
        raceline['u_s'][j]      = value(m.u1[s])

    DF = pd.DataFrame()
    # for v in m.component_objects(Var, active=True):
    #     for index in v:
    #         DF.at[index, v.name] = value(v[index])

    return raceline, DF

def getValues(DF, s):
    m1x4 = np.interp(s, DF.index, DF['x4'].values)
    m1x5 = np.interp(s, DF.index, DF['x5'].values)

    return m1x4, m1x5

def plot_racelines(track, racelines, colors):
    import matplotlib.pyplot as plt
    import os

    fig_xy = plt.figure(figsize=(20, 20))
    ax = fig_xy.gca()
    track.plot_map(ax)

    fig_ts = plt.figure(figsize=(50, 30))
    ax_vx = fig_ts.add_subplot(6, 1, 1)
    ax_vx.set_ylabel('vel long')
    ax_vy = fig_ts.add_subplot(6, 1, 2)
    ax_vy.set_ylabel('vel lat')
    ax_pd = fig_ts.add_subplot(6, 1, 3)
    ax_pd.set_ylabel('yaw rate')
    ax_ey = fig_ts.add_subplot(6, 1, 4)
    ax_ey.set_ylabel('e_y')
    ax_a = fig_ts.add_subplot(6, 1, 5)
    ax_a.set_ylabel('accel cmd')
    ax_s = fig_ts.add_subplot(6, 1, 6)
    ax_s.set_ylabel('steer cmd')
    ax_s.set_xlabel('s')

    for r, c in zip(racelines, colors):
        vs = np.cos(r['v_long']) - np.sin(r['v_tran'])
        speed = np.sqrt(np.power(r['v_long'],2)+np.power(r['v_tran'],2))
        sc = ax.scatter(r['x'], r['y'], c=speed)
        ax.plot(r['x'], r['y'], c)

        ax_vx.plot(r['s'], r['v_long'], c)
        ax_vy.plot(r['s'], r['v_tran'], c)
        ax_pd.plot(r['s'], r['psidot'], c)
        ax_ey.plot(r['s'], r['e_y'], c)
        ax_a.plot(r['s'], r['u_a'], c)
        ax_s.plot(r['s'], r['u_s'], c)

        laptime = r['t'][-1]
        print(f'Lap time: {laptime}')

    ax.set_aspect('equal')
    plt.colorbar(mappable=sc, ax=ax)
    ax.set_title(f'time: {laptime:.2f} s')

    save_plot = False
    if save_plot:
        fig_xy.savefig(os.path.join(os.path.expanduser('~'), 'Pictures', 'raceline.png'))
        fig_ts.savefig(os.path.join(os.path.expanduser('~'), 'Pictures', 'raceline_state_input.png'))

    plt.draw()
    plt.show()
In [ ]
from google.colab import files
import os
from mpclab_common.models.model_types import DynamicBicycleConfig
from mpclab_common.track import get_track

track_name = 'L_track_barc'
track_obj = get_track(track_name)

vehicle_config = DynamicBicycleConfig(wheel_dist_front=0.132,
                                    wheel_dist_rear=0.128,
                                    mass=2.258, #2.158,
                                    yaw_inertia=0.01771, #0.026188,
                                    drag_coefficient=0.0,
                                    tire_model='pacejka',
                                    wheel_friction=0.9, #0.96,
                                    pacejka_b_front=5.0, #0.99,
                                    pacejka_b_rear=575055782097995, #0.99,
                                    pacejka_c_front=2.28, #11.04,
                                    pacejka_c_rear=2.0524659447890445, #11.04,
                                    simple_slip=False)

VW = 0.195
track_tightening = VW/2

state_input_ub = VehicleState(p=ParametricPose(x_tran=1e9, e_psi=np.pi/2),
                                v=BodyLinearVelocity(v_long=5, v_tran=1),
                                w=BodyAngularVelocity(w_psi=10),
                                u=VehicleActuation(u_a=2.0, u_steer=0.436))
state_input_lb = VehicleState(p=ParametricPose(x_tran=-1e9, e_psi=-np.pi/2),
                                v=BodyLinearVelocity(v_long=0.1, v_tran=-1),
                                w=BodyAngularVelocity(w_psi=-10),
                                u=VehicleActuation(u_a=-2.0, u_steer=-0.436))
input_rate_ub = VehicleActuation(u_a=40, u_steer=4.5)
input_rate_lb = VehicleActuation(u_a=-40, u_steer=-4.5)
vehicle_constraints = {'xu_ub': state_input_ub, 'xu_lb': state_input_lb, 'du_ub': input_rate_ub, 'du_lb': input_rate_lb}

obj_num = 0
raceline_1, raceline_data = compute_raceline(track_obj, vehicle_config, vehicle_constraints,
                                                obj_num=obj_num,
                                                track_tightening=track_tightening,
                                                constant_width=True)
# raceline_2, _ = compute_raceline(track_name, vehicle_config, vehicle_constraints, raceline_to_avoid=raceline_data)

plot = True
save = True

if save:
    # save_dir = os.path.expanduser('~/barc_data/racelines')
    # if not os.path.exists(save_dir):
        # os.makedirs(save_dir)
    save_dir = ''

    # filename = track_name + '_raceline.npz'
    filename = 'raceline.npz'
    save_path = filename
    # save_path = os.path.join(save_dir, filename)
    print('Saving computed raceline to %s' % save_path)
    np.savez(save_path, **raceline_1)
    files.download(save_path)

    # filename = track_name + '_raceline_2.npz'
    # save_path = os.path.join(save_dir, filename)
    # print('Saving computed raceline to %s' % save_path)
    # np.savez(save_path, **raceline_2)

if plot:
    # plot_racelines(track_name, [raceline_1, raceline_2], ['b', 'r'])
    plot_racelines(track_obj, [raceline_1], ['b'])
=================== Raceline computation ===================
Track is a circuit
Using constant width
Vehicle configuration:
     - center of mass to front axle: 0.132 m
     - center of mass to rear axle: 0.128 m
     - mass: 2.258 kg
     - yaw inertia: 0.01771 kg m^2
     - tire model: pacejka
     - tire friction coefficient: 0.9
     - Front tires:
         - B: 5
         - C: 2.28
         - D: 9.81459
     - Rear tires:
         - B: 5.75056e+14
         - C: 2.05247
         - D: 10.1213
Vehicle input constraints:
     - max acceleration: 2 m/s^2
     - min acceleration: -2 m/s^2
     - max steering angle: 0.436 rad
     - min steering angle: -0.436 rad
Vehicle state constraints:
     - max longitudinal velocity: 5 m/s
     - min longitudinal velocity: 0.1 m/s
     - max lateral velocity: 1 m/s
     - min lateral velocity: -1 m/s
     - max yaw rate: 10 rad/s
     - min yaw rate: -10 rad/s
============================================================
Ipopt 3.12.13: tol=0.01
mu_init=1e-06


******************************************************************************
This program contains Ipopt, a library for large-scale nonlinear optimization.
 Ipopt is released as open source code under the Eclipse Public License (EPL).
         For more information visit http://projects.coin-or.org/Ipopt
******************************************************************************

This is Ipopt version 3.12.13, running with linear solver mumps.
NOTE: Other linear solvers might be more efficient (see Ipopt documentation).

Number of nonzeros in equality constraint Jacobian...:   352166
Number of nonzeros in inequality constraint Jacobian.:        0
Number of nonzeros in Lagrangian Hessian.............:    84302

Total number of variables............................:    48422
                     variables with only lower bounds:        0
                variables with lower and upper bounds:    26412
                     variables with only upper bounds:        0
Total number of equality constraints.................:    44018
Total number of inequality constraints...............:        0
        inequality constraints with only lower bounds:        0
   inequality constraints with lower and upper bounds:        0
        inequality constraints with only upper bounds:        0

iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
   0  9.9999900e-03 9.09e+00 1.00e+00  -6.0 0.00e+00    -  0.00e+00 0.00e+00   0
   1  3.1572035e-02 6.69e+00 3.51e+00  -6.0 1.01e+01    -  7.19e-02 4.43e-01h  1
   2  4.4869538e-02 4.63e+00 4.15e+00  -6.0 6.52e+00    -  8.04e-02 4.17e-01h  1
   3  7.3945501e-02 2.82e+00 6.66e+00  -6.0 7.66e+00    -  1.06e-01 7.71e-01h  1
   4  7.3911159e-02 2.82e+00 6.81e+00  -6.0 4.28e+00    -  8.34e-02 7.67e-04h  1
   5  7.1934154e-02 2.26e+00 6.32e+01  -6.0 4.54e+00    -  5.88e-03 2.52e-01h  1
   6  7.1930295e-02 2.24e+00 6.28e+01  -6.0 3.15e+00    -  1.96e-03 6.05e-03h  1
   7  7.1927916e-02 2.23e+00 6.49e+01  -6.0 3.13e+00    -  1.15e-05 4.32e-03h  1
   8  7.1809601e-02 1.85e+00 5.34e+01  -6.0 3.13e+00    -  2.96e-03 2.08e-01h  1
   9  7.1263996e-02 1.78e+00 4.91e+01  -6.0 2.70e+00    -  1.60e-01 3.95e-02h  1
iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
  10  8.4345128e-02 1.46e+00 4.30e+01  -6.0 2.76e+00    -  7.35e-02 2.16e-01h  1
  11  1.0531832e-01 1.32e+00 3.83e+01  -6.0 2.90e+00    -  1.07e-01 1.07e-01h  1
  12r 1.0531832e-01 1.32e+00 9.99e+02   0.1 0.00e+00    -  0.00e+00 3.80e-07R  4
  13r 1.0848182e-01 2.22e+00 9.99e+02   0.1 5.27e+04    -  1.09e-07 2.10e-05f  1
  14r 2.1846817e-01 2.11e+00 9.99e+02   0.1 9.44e+02    -  7.84e-05 6.34e-04f  1
  15r 4.5584060e-01 1.74e+00 9.98e+02   0.1 4.83e+02    -  1.46e-04 1.15e-03f  1
  16r 5.1600325e-01 1.60e+00 9.98e+02   0.1 3.51e+02    -  4.72e-04 3.96e-04f  1
  17r 7.5965114e-01 1.10e+00 9.97e+02   0.1 3.92e+02    -  4.63e-04 1.66e-03f  1
  18  7.6887630e-01 9.94e-01 1.00e+03  -6.0 6.44e+00    -  3.87e-02 1.04e-01h  1
  19  7.6896332e-01 9.92e-01 9.99e+02  -6.0 1.08e+01    -  6.38e-02 1.92e-03h  1
iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
  20  7.7707682e-01 9.45e-01 1.11e+03  -6.0 1.04e+01    -  4.45e-02 4.96e-02h  1
  21  8.3571486e-01 7.80e-01 3.96e+03  -6.0 9.58e+00    -  9.13e-03 2.02e-01h  1
  22  8.6211344e-01 7.30e-01 3.95e+03  -6.0 1.15e+01    -  3.42e-03 6.82e-02h  1
  23  8.8033905e-01 7.01e-01 3.87e+03  -6.0 1.31e+01    -  1.20e-03 4.07e-02h  1
  24  8.8160833e-01 6.99e-01 3.86e+03  -6.0 1.42e+01    -  2.60e-03 2.62e-03h  1
  25  8.8396643e-01 6.96e-01 3.84e+03  -6.0 1.43e+01    -  4.25e-03 4.79e-03h  1
  26  9.0179059e-01 6.72e-01 3.75e+03  -6.0 1.46e+01    -  1.25e-03 3.52e-02h  1
  27  9.0468934e-01 6.68e-01 3.73e+03  -6.0 1.51e+01    -  2.25e-02 5.36e-03h  1
  28  9.2716114e-01 6.44e-01 3.62e+03  -6.0 1.52e+01    -  1.60e-03 3.71e-02h  1
  29  9.2719227e-01 6.44e-01 3.62e+03  -6.0 1.58e+01    -  9.27e-04 4.80e-05h  1
iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
  30  9.2852529e-01 6.43e-01 3.62e+03  -6.0 1.59e+01    -  3.14e-06 2.01e-03h  1
  31  9.4099349e-01 6.31e-01 3.55e+03  -6.0 1.59e+01    -  9.33e-03 1.86e-02h  1
  32  9.5352287e-01 6.20e-01 3.49e+03  -6.0 1.62e+01    -  4.79e-03 1.73e-02h  1
  33  9.5834565e-01 6.16e-01 3.47e+03  -6.0 1.72e+01    -  3.17e-03 6.27e-03h  1
  34  9.6146076e-01 6.14e-01 3.46e+03  -6.0 1.71e+01    -  1.29e-03 3.92e-03h  1
  35  1.0116767e+00 5.96e-01 3.23e+03  -6.0 1.71e+01    -  2.07e-03 6.21e-02h  1
  36  1.0996134e+00 7.29e-01 1.18e+04  -6.0 1.79e+01    -  2.78e-04 9.20e-02h  1
  37  1.1046400e+00 7.27e-01 1.18e+04  -6.0 1.54e+01    -  5.33e-04 3.72e-03h  1
  38  1.1151869e+00 7.22e-01 1.17e+04  -6.0 1.52e+01    -  1.03e-02 7.56e-03h  1
  39  1.1186232e+00 7.20e-01 1.16e+04  -6.0 1.43e+01    -  3.36e-03 2.14e-03h  1
iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
  40  1.1363301e+00 7.14e-01 1.12e+04  -6.0 1.39e+01    -  3.32e-03 1.06e-02h  1
  41  1.1452951e+00 7.11e-01 1.11e+04  -6.0 1.30e+01    -  1.80e-03 4.79e-03h  1
  42  1.1491988e+00 7.09e-01 1.11e+04  -6.0 1.25e+01    -  7.46e-04 1.98e-03h  1
  43  1.1663316e+00 7.04e-01 1.10e+04  -6.0 1.24e+01    -  2.78e-03 8.51e-03h  1
  44  1.2638871e+00 6.85e-01 1.05e+04  -6.0 1.16e+01    -  4.21e-04 4.48e-02h  1
  45  1.3986007e+00 6.59e-01 1.00e+04  -6.0 8.79e+00    -  4.38e-04 4.94e-02h  1
  46  1.7365862e+00 6.04e-01 8.97e+03  -6.0 6.50e+00    -  8.95e-04 1.08e-01h  1
  47  1.8099186e+00 5.91e-01 8.79e+03  -6.0 3.83e+00    -  2.17e-03 2.18e-02h  1
  48  1.9276290e+00 5.71e-01 8.48e+03  -6.0 3.52e+00    -  1.79e-02 3.50e-02h  1
  49  2.0517348e+00 5.51e-01 8.20e+03  -6.0 3.38e+00    -  5.39e-03 3.68e-02h  1
iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
  50  2.2660329e+00 5.16e-01 7.69e+03  -6.0 3.32e+00    -  1.39e-02 6.45e-02h  1
  51  4.6559408e+00 2.01e-01 5.76e+03  -6.0 3.21e+00    -  1.17e-02 7.46e-01h  1
  52  5.1631507e+00 9.59e-02 2.90e+03  -6.0 9.05e-01    -  1.41e-01 5.61e-01h  1
  53  5.5608979e+00 5.31e-03 2.59e+02  -6.0 3.98e-01    -  1.70e-01 1.00e+00h  1
  54  5.5576026e+00 1.93e-05 1.35e+00  -6.0 1.13e-02    -  9.83e-01 1.00e+00h  1
  55  5.4825841e+00 5.46e-03 6.03e+01  -6.0 7.20e-01    -  2.45e-01 3.89e-01f  1
  56  5.2802986e+00 4.19e-02 5.35e+02  -6.0 8.98e-01    -  1.11e-01 8.89e-01h  1
  57  5.1678749e+00 3.23e-02 5.15e+02  -6.0 8.11e-01    -  9.39e-02 6.10e-01h  1
  58  4.9922355e+00 4.01e-02 9.23e+02  -6.0 8.06e-01    -  3.32e-01 1.00e+00h  1
  59  4.8949123e+00 3.86e-02 9.18e+02  -6.0 1.06e+00    -  3.27e-01 4.89e-01h  1
iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
  60  4.8412097e+00 3.73e-02 8.81e+02  -6.0 1.58e+00    -  1.88e-01 2.15e-01h  1
  61  4.6658455e+00 1.00e-01 2.40e+03  -6.0 1.95e+00    -  4.86e-02 6.31e-01h  1
  62  4.6202012e+00 9.03e-02 2.13e+03  -6.0 1.92e+00    -  1.15e-01 2.23e-01h  1
  63  4.5484614e+00 9.37e-02 2.11e+03  -6.0 2.09e+00    -  1.45e-01 3.39e-01h  1
  64  4.5308277e+00 8.82e-02 1.98e+03  -6.0 2.44e+00    -  1.05e-01 8.33e-02h  1
  65  4.5132220e+00 8.36e-02 1.88e+03  -6.0 2.66e+00    -  1.48e-01 7.80e-02h  1
  66  4.4750013e+00 8.06e-02 1.84e+03  -6.0 3.19e+00    -  1.30e-01 1.52e-01h  1
  67  4.4579902e+00 7.70e-02 1.77e+03  -6.0 3.26e+00    -  5.68e-02 6.57e-02h  1
  68  4.3846131e+00 1.08e-01 2.78e+03  -6.0 3.67e+00    -  4.04e-02 2.80e-01h  1
  69  4.3140100e+00 1.69e-01 3.81e+03  -6.0 2.94e+00    -  7.24e-02 3.21e-01h  1
iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
  70  4.2117666e+00 3.59e-01 6.02e+03  -6.0 2.52e+00    -  1.77e-01 5.53e-01h  1
  71  4.1764605e+00 2.81e-01 4.53e+03  -6.0 2.75e+00    -  2.66e-01 2.46e-01h  1
  72  4.1333333e+00 2.09e-01 3.88e+03  -6.0 3.56e+00    -  1.35e-01 2.66e-01h  1
  73  4.1090372e+00 1.80e-01 3.56e+03  -6.0 3.41e+00    -  2.70e-01 1.62e-01h  1
  74  4.0909993e+00 1.67e-01 3.34e+03  -6.0 4.12e+00    -  8.68e-02 1.06e-01h  1
  75  4.0600329e+00 1.67e-01 3.25e+03  -6.0 4.10e+00    -  1.70e-02 1.88e-01h  1
  76  4.0403447e+00 1.65e-01 3.01e+03  -6.0 4.03e+00    -  1.83e-02 1.41e-01h  1
  77  4.0391952e+00 1.64e-01 2.98e+03  -6.0 4.27e+00    -  5.36e-02 9.16e-03h  1
  78  4.0307625e+00 1.57e-01 2.82e+03  -6.0 4.63e+00    -  1.65e-02 6.48e-02h  1
  79  3.9918892e+00 2.45e-01 3.40e+03  -6.0 3.74e+00    -  1.65e-02 3.19e-01h  1
iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
  80  3.9784878e+00 2.31e-01 3.10e+03  -6.0 4.62e+00    -  5.33e-02 1.39e-01h  1
  81  3.9607947e+00 2.17e-01 2.81e+03  -6.0 3.82e+00    -  4.23e-02 2.00e-01h  1
  82  3.9577566e+00 2.10e-01 2.71e+03  -6.0 4.67e+00    -  9.01e-02 3.77e-02h  1
  83  3.9423369e+00 1.99e-01 2.40e+03  -6.0 4.28e+00    -  5.63e-02 1.87e-01h  1
  84  3.9145730e+00 2.74e-01 3.19e+03  -6.0 3.59e+00    -  3.41e-02 3.80e-01h  1
  85  3.9062579e+00 2.49e-01 2.84e+03  -6.0 4.75e+00    -  8.43e-02 1.42e-01h  1
  86  3.8968108e+00 2.20e-01 2.52e+03  -6.0 4.18e+00    -  8.15e-02 1.72e-01h  1
  87  3.8842809e+00 1.99e-01 2.13e+03  -6.0 4.07e+00    -  6.19e-02 2.46e-01h  1
  88  3.8592982e+00 3.01e-01 3.23e+03  -6.0 3.53e+00    -  4.89e-03 5.75e-01h  1
  89  3.8575554e+00 2.82e-01 3.02e+03  -6.0 3.59e+00    -  1.52e-01 6.77e-02h  1
iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
  90  3.8572684e+00 2.79e-01 2.98e+03  -6.0 3.27e+00    -  8.01e-03 1.15e-02h  1
  91  3.8527871e+00 2.33e-01 2.62e+03  -6.0 3.49e+00    -  1.03e-01 1.78e-01f  1
  92  3.8513272e+00 2.17e-01 2.47e+03  -6.0 3.42e+00    -  3.10e-01 6.84e-02h  1
  93  3.8476457e+00 1.86e-01 2.21e+03  -6.0 3.84e+00    -  2.75e-02 1.51e-01h  1
  94  3.8426879e+00 1.50e-01 2.01e+03  -6.0 3.88e+00    -  2.96e-02 2.06e-01h  1
  95  3.8337078e+00 1.01e-01 1.93e+03  -6.0 3.70e+00    -  5.09e-02 4.54e-01h  1
  96  3.8297528e+00 7.99e-02 1.57e+03  -6.0 3.56e+00    -  2.34e-01 2.58e-01f  1
  97  3.8273275e+00 7.05e-02 1.36e+03  -6.0 3.69e+00    -  1.29e-01 1.69e-01h  1
  98  3.8253628e+00 6.38e-02 1.20e+03  -6.0 4.56e+00    -  3.21e-01 1.41e-01h  1
  99  3.8216824e+00 6.01e-02 1.00e+03  -6.0 5.30e+00    -  1.04e-01 2.49e-01h  1
iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
 100  3.8139765e+00 1.17e-01 1.32e+03  -6.0 5.18e+00    -  6.09e-02 6.18e-01h  1
 101  3.8128674e+00 1.01e-01 1.11e+03  -6.0 5.56e+00    -  2.68e-01 1.70e-01h  1
 102  3.8114498e+00 8.55e-02 9.25e+02  -6.0 6.55e+00    -  1.59e-01 2.11e-01h  1
 103  3.8074545e+00 1.01e-01 1.39e+03  -6.0 6.25e+00    -  8.28e-02 6.51e-01h  1
 104  3.8065533e+00 7.53e-02 1.18e+03  -6.0 6.64e+00    -  4.34e-01 2.91e-01h  1
 105  3.8044431e+00 1.19e-01 3.16e+03  -6.0 9.60e+00    -  2.15e-01 5.93e-01h  1
 106  3.8034220e+00 1.24e-01 3.55e+03  -6.0 9.90e+00    -  6.39e-01 4.69e-01h  1
 107  3.8034681e+00 4.80e-03 6.41e+01  -6.0 9.94e-02  -4.0 8.60e-01 1.00e+00h  1
 108  3.8034160e+00 4.42e-04 2.66e+00  -6.0 9.29e-02  -4.5 1.00e+00 1.00e+00h  1
 109  3.8033221e+00 1.51e-03 1.29e+01  -6.0 2.12e-01  -5.0 1.00e+00 1.00e+00h  1
iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
 110  3.8031530e+00 3.99e-03 5.44e+01  -6.0 5.63e-01  -5.4 1.00e+00 1.00e+00h  1
 111  3.8028225e+00 1.02e-02 8.38e+01  -6.0 1.63e+00  -5.9 1.00e+00 1.00e+00h  1
 112  3.8022769e+00 4.31e-02 1.60e+02  -6.0 4.13e+00  -6.4 1.00e+00 8.00e-01h  1
 113  3.8016232e+00 1.27e-01 4.97e+02  -6.0 1.07e+01  -6.9 1.00e+00 4.98e-01h  1
 114  3.8013055e+00 8.24e-02 3.41e+02  -6.0 3.45e+00  -6.4 8.29e-01 1.00e+00h  1
 115  3.8008742e+00 2.55e-01 7.85e+02  -6.0 7.40e+00  -6.9 1.00e+00 6.70e-01h  1
 116  3.8007376e+00 1.53e-01 2.62e+02  -6.0 2.32e+00  -6.5 8.93e-01 1.00e+00h  1
 117  3.8004190e+00 6.87e-01 6.75e+02  -6.0 5.61e+00  -7.0 1.00e+00 1.00e+00h  1
 118  3.8001741e+00 3.18e-01 2.78e+02  -6.0 1.12e+01  -7.4 1.00e+00 7.95e-01h  1
 119  3.8001645e+00 4.79e-03 4.54e+01  -6.0 2.76e+00  -7.0 1.00e+00 1.00e+00h  1
iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
 120  3.8000691e+00 7.12e-03 6.03e+01  -6.0 7.44e+00  -7.5 1.00e+00 1.00e+00h  1
 121  3.8000519e+00 1.18e-03 1.16e+01  -6.0 1.74e+00  -7.1 1.00e+00 1.00e+00h  1
 122  3.8000092e+00 5.37e-03 3.67e+01  -6.0 3.90e+00  -7.5 1.00e+00 1.00e+00h  1
 123  3.8000004e+00 5.14e-04 5.24e+00  -6.0 1.19e+00  -7.1 1.00e+00 1.00e+00h  1
 124  3.7999904e+00 4.19e-04 5.05e+00  -6.0 1.17e+00  -7.2 1.00e+00 1.00e+00h  1
 125  3.7999706e+00 1.21e-03 2.26e+01  -6.0 2.80e+00  -7.6 1.00e+00 1.00e+00h  1
 126  3.7999667e+00 1.13e-04 2.24e+00  -6.0 9.09e-01  -7.2 1.00e+00 1.00e+00h  1
 127  3.7999563e+00 5.90e-04 1.84e+01  -6.0 1.96e+00  -7.7 1.00e+00 1.00e+00h  1
 128  3.7999541e+00 6.97e-05 2.35e+00  -6.0 6.72e-01  -7.3 1.00e+00 1.00e+00h  1
 129  3.7999481e+00 5.38e-04 1.90e+01  -6.0 1.66e+00  -7.7 1.00e+00 1.00e+00h  1
iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
 130  3.7999397e+00 2.97e-03 1.14e+02  -6.0 3.83e+00  -8.2 1.00e+00 1.00e+00h  1
 131  3.7999360e+00 1.06e-03 1.24e+01  -6.0 3.90e+00  -7.8 1.00e+00 1.00e+00h  1
 132  3.7999368e+00 1.26e-04 1.66e+00  -6.0 1.07e+00  -7.4 1.00e+00 1.00e+00h  1
 133  3.7999356e+00 2.77e-04 1.32e+01  -6.0 7.93e-01  -7.9 1.00e+00 1.00e+00h  1
 134  3.7999357e+00 3.62e-05 1.67e+00  -6.0 3.69e-01  -7.4 1.00e+00 1.00e+00h  1
 135  3.7999342e+00 2.40e-04 1.27e+01  -6.0 6.38e-01  -7.9 1.00e+00 1.00e+00h  1
 136  3.7999340e+00 2.88e-05 1.61e+00  -6.0 2.06e-01  -7.5 1.00e+00 1.00e+00h  1
 137  3.7999337e+00 3.85e-06 2.22e-01  -6.0 8.06e-02  -7.1 1.00e+00 1.00e+00h  1
 138  3.7999334e+00 3.14e-05 1.86e+00  -6.0 2.18e-01  -7.5 1.00e+00 1.00e+00h  1
 139  3.7999323e+00 2.10e-04 1.32e+01  -6.0 5.61e-01  -8.0 1.00e+00 1.00e+00h  1
iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
 140  3.7999320e+00 2.43e-05 1.65e+00  -6.0 1.91e-01  -7.6 1.00e+00 1.00e+00h  1
 141  3.7999312e+00 1.58e-04 1.16e+01  -6.0 4.87e-01  -8.1 1.00e+00 1.00e+00h  1
 142  3.7999309e+00 1.80e-05 1.45e+00  -6.0 1.68e-01  -7.6 1.00e+00 1.00e+00h  1
 143  3.7999303e+00 1.13e-04 9.86e+00  -6.0 4.10e-01  -8.1 1.00e+00 1.00e+00h  1
 144  3.7999300e+00 1.26e-05 1.22e+00  -6.0 1.48e-01  -7.7 1.00e+00 1.00e+00h  1
 145  3.7999295e+00 7.52e-05 8.02e+00  -6.0 3.74e-01  -8.2 1.00e+00 1.00e+00h  1
 146  3.7999292e+00 9.56e-06 9.89e-01  -6.0 3.88e-01  -7.7 1.00e+00 1.00e+00h  1
 147  3.7999291e+00 4.66e-05 2.40e+00  -6.0 1.18e+00  -8.2 1.00e+00 5.00e-01h  2
 148  3.7999290e+00 6.77e-05 2.41e+00  -6.0 2.49e+03  -8.7 7.16e-03 4.31e-04h  5
 149  3.7999287e+00 9.63e-06 1.46e-01  -6.0 1.44e-01  -7.4 1.00e+00 1.00e+00h  1
iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
 150  3.7999288e+00 7.53e-06 1.09e+00  -6.0 1.71e-01  -7.8 1.00e+00 1.00e+00h  1
 151  3.7999287e+00 9.34e-07 1.43e-01  -6.0 3.61e-02  -7.4 1.00e+00 1.00e+00h  1
 152  3.7999285e+00 6.69e-06 1.08e+00  -6.0 2.36e-01  -7.9 1.00e+00 1.00e+00h  1
 153  3.7999285e+00 2.22e-05 1.03e+00  -6.0 1.08e+01  -8.4 1.00e+00 6.25e-02h  5
 154  3.7999280e+00 2.02e-05 1.09e+00  -6.0 5.56e-01  -7.9 1.00e+00 1.00e+00h  1
 155  3.7999282e+00 4.08e-05 8.15e-01  -6.0 1.44e+00  -8.0 1.00e+00 5.00e-01h  2
 156  3.7999281e+00 5.04e-06 1.60e-01  -6.0 2.66e-01  -7.6 1.00e+00 1.00e+00h  1
 157  3.7999282e+00 6.75e-06 1.11e+00  -6.0 3.17e-01  -8.0 1.00e+00 1.00e+00h  1
 158  3.7999281e+00 3.51e-05 1.09e+00  -6.0 9.76e+00  -8.1 1.00e+00 1.56e-02h  7
 159  3.7999281e+00 1.42e-05 2.14e-01  -6.0 3.00e-01  -7.7 1.00e+00 1.00e+00h  1
iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
 160  3.7999281e+00 1.11e-05 2.08e-01  -6.0 1.47e+00  -8.1 1.00e+00 2.50e-01h  3
 161  3.7999279e+00 2.26e-06 1.87e-01  -6.0 2.04e-01  -7.7 1.00e+00 1.00e+00h  1
 162  3.7999280e+00 8.90e-08 2.47e-02  -6.0 2.74e-02  -7.3 1.00e+00 1.00e+00h  1

Number of Iterations....: 162

                                   (scaled)                 (unscaled)
Objective...............:   3.7999279551688945e+00    3.7999279551688945e+00
Dual infeasibility......:   2.4688370976946317e-02    2.4688370976946317e-02
Constraint violation....:   8.6727506065642501e-08    8.8979141721345898e-08
Complementarity.........:   1.0000000520168491e-06    1.0000000520168491e-06
Overall NLP error.......:   3.0869858031594439e-03    2.4688370976946317e-02


Number of objective function evaluations             = 194
Number of objective gradient evaluations             = 159
Number of equality constraint evaluations            = 194
Number of inequality constraint evaluations          = 0
Number of equality constraint Jacobian evaluations   = 164
Number of inequality constraint Jacobian evaluations = 0
Number of Lagrangian Hessian evaluations             = 162
Total CPU secs in IPOPT (w/o function evaluations)   =    110.476
Total CPU secs in NLP function evaluations           =     19.853

EXIT: Optimal Solution Found.
Saving computed raceline to raceline.npz
&lt;IPython.core.display.Javascript object&gt;
&lt;IPython.core.display.Javascript object&gt;
Lap time: 3.7999279551688945
Notebook output image
Notebook output image
In [ ]
Powered by Hugo Blox Kit - https://github.com/HugoBlox/kit
Shengfan Cao
Authors
PhD Researcher in Autonomous Driving & Robotics
PhD researcher with 3+ years of hands-on experience in autonomous driving and robotic systems, spanning safe learning, control, and end-to-end autonomy deployment. I am transitioning into industry to work where large-scale data and real-world constraints continuously shape and validate learning-based autonomous systems.