Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion acc_project/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ class PID:

def update(self, error: float, dt: float) -> float:
self._integral += error * dt
self._integral = max(self.integral_min, min(self.integral_max, self._integral))
# Clamp the integral term to reduce windup when the error persists.
self._integral = max(
self.integral_min, min(self.integral_max, self._integral)
)
derivative = (error - self._previous_error) / dt if dt > 0.0 else 0.0
self._previous_error = error
return self.kp * error + self.ki * self._integral + self.kd * derivative
Expand All @@ -37,19 +40,24 @@ def compute_accel(
lead_speed: float,
dt: float,
) -> tuple[float, dict]:
# Speed loop: track the desired cruise speed when the road ahead is free.
speed_error = self.cruise_speed - ego_speed
desired_gap = self.desired_gap(ego_speed)
# Gap loop: positive gap_error means the actual distance is larger than required.
gap_error = gap - desired_gap
relative_speed = lead_speed - ego_speed

cruise_accel = self.speed_pid.update(speed_error, dt)
# Relative speed is blended into the spacing loop to make the response less myopic.
gap_accel = self.gap_pid.update(gap_error + 0.6 * relative_speed, dt)

if gap < desired_gap + 5.0:
# Near the lead car, prioritize spacing so safety dominates comfort/speed tracking.
commanded_accel = min(cruise_accel, gap_accel)
mode = "gap-control"
else:
commanded_accel = cruise_accel
# Far from the lead car, behave like a standard cruise controller.
mode = "speed-control"

telemetry = {
Expand Down
12 changes: 10 additions & 2 deletions acc_project/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,19 @@ class EgoVehicleModel:
dt: float
max_accel: float = 2.5
max_decel: float = -4.0
# This is a simple linear drag surrogate for learning purposes.
drag_coeff: float = 0.03

def step(self, state: VehicleState, commanded_accel: float) -> VehicleState:
accel = max(self.max_decel, min(self.max_accel, commanded_accel))
net_accel = accel - self.drag_coeff * state.speed
# Actuator/vehicle limits: the controller may ask for more than the car can produce.
accel = max(
self.max_decel, min(self.max_accel, commanded_accel)
)
# A more detailed longitudinal model often includes rolling resistance and v^2 aero drag.
# Here we keep a linear term so the first project stays easy to read and tune.
net_accel = (
accel - self.drag_coeff * state.speed
)
next_speed = max(0.0, state.speed + net_accel * self.dt)
next_position = state.position + next_speed * self.dt
return VehicleState(
Expand Down
19 changes: 13 additions & 6 deletions acc_project/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@

@dataclass
class SimulationConfig:
# dataclass is a lightweight way to bundle simulation parameters together.
total_time: float = 30.0
dt: float = 0.1
initial_gap: float = 35.0
ego_initial_speed: float = 20.0


def run_simulation(config: SimulationConfig) -> list[dict]:
# list[dict] is a type hint: this function returns a list of telemetry records.
scenario = LeadVehicleScenario(initial_position=config.initial_gap)
ego_model = EgoVehicleModel(dt=config.dt)
controller = ACCController()
Expand All @@ -33,11 +35,14 @@ def run_simulation(config: SimulationConfig) -> list[dict]:
lead.position += lead.speed * config.dt

gap = lead.position - ego.position
command_accel, telemetry = controller.compute_accel(
ego_speed=ego.speed,
gap=gap,
lead_speed=lead.speed,
dt=config.dt,
command_accel, telemetry = (
# The controller uses spacing and relative-speed errors to compute a desired accel.
controller.compute_accel(
ego_speed=ego.speed,
gap=gap,
lead_speed=lead.speed,
dt=config.dt,
)
)

ego = ego_model.step(ego, command_accel)
Expand Down Expand Up @@ -65,7 +70,9 @@ def summarize(history: list[dict]) -> dict:
min_gap = min(row["gap_m"] for row in history)
max_speed = max(row["ego_speed_mps"] for row in history)
avg_gap_error = sum(abs(row["gap_error_m"]) for row in history) / len(history)
gap_control_ratio = sum(1 for row in history if row["mode"] == "gap-control") / len(history)
gap_control_ratio = sum(1 for row in history if row["mode"] == "gap-control") / len(
history
)
return {
"min_gap_m": min_gap,
"max_ego_speed_mps": max_speed,
Expand Down
8 changes: 7 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
from pathlib import Path

from acc_project.simulation import SimulationConfig, plot_history, run_simulation, summarize, write_csv
from acc_project.simulation import (
SimulationConfig,
plot_history,
run_simulation,
summarize,
write_csv,
)


def main() -> None:
Expand Down