Car following with delay
A chain of cars that react to the speed difference to the car ahead after a reaction time.
- Name:
delayed_car_following - Field:
engineering/traffic - Formalism: ordinary differential equations
- References: Gros 2015; Chandler, Herman, and Montroll 1958
Factory
@register_model(
field='engineering/traffic',
tags=('traffic', 'delay', 'car following', 'instability'),
references=('Gros 2015', 'Chandler, Herman, and Montroll 1958'),
defaults={'until': 60.0, 'dt': 0.5},
)
def delayed_car_following(
n_cars: int = 8,
*,
spacing: float = 10.0,
u: float = 1.0,
amplitude: float = 0.1,
omega: float = 0.5,
reaction: float = 1.0,
T: float = 1.0,
power: float = 0.0,
) -> System:
"""Return a chain of cars that react to the speed difference to the car ahead after a reaction time.
Car `j + 1` follows car `j` as `x''_{j+1}(t) = λ Δv(t - T) / Δx(t - T)^power`, where `Δv` and `Δx` are the speed
and position differences to the car ahead. The lead car drives at `u (1 - amplitude sin(ω t))`. Oscillations grow
along the chain when `ω < 2 λ sin(ω T)` for the linear model, `power = 0`.
Args:
n_cars:
The number of cars, the lead car included.
spacing:
The initial distance between neighbouring cars.
u:
The mean speed of the lead car.
amplitude:
The relative amplitude of the speed oscillation of the lead car.
omega:
The angular frequency of the speed oscillation of the lead car.
reaction:
The reaction constant `λ`.
T:
The reaction time.
power:
The power of the distance in the reaction, 0 for the linear model.
Returns:
system:
The model, with the lead car `car_0` and the following cars `car_<j>`, each with position `x` and speed `v`.
Examples:
>>> from skcomplex.models.engineering.traffic import delayed_car_following
>>> delayed_car_following(n_cars=3).elements[2].x
-20.0
"""
lead = LeadCarElement('car_0', x=0.0, v=u, u=u, amplitude=amplitude, omega=omega)
followers = [CarElement(f'car_{index}', x=-index * spacing, v=u) for index in range(1, n_cars)]
interactions = [
DelayedFollowingInteraction(
f'follow_{index}',
target=f'car_{index}',
leader=f'car_{index - 1}',
reaction=reaction,
T=T,
power=power,
)
for index in range(1, n_cars)
]
return System(elements=[lead, *followers], interactions=interactions)
Components
class LeadCarElement(Element):
"""Lead car whose speed swings about a cruising speed `u` with an amplitude and a frequency."""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
x: Annotated[object, StateSpec(role='position')] = 0.0,
v: Annotated[object, StateSpec(role='velocity')] = 1.0,
u: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
amplitude: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.1,
omega: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.5,
) -> None:
super().__init__(label, visual=visual)
self.x = x
self.v = v
self.u = u
self.amplitude = amplitude
self.omega = omega
def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
"""Return the advance of the position and the forced swing of the speed."""
return [RateLaw(s.x, s.v), RateLaw(s.v, -s.u * s.amplitude * s.omega * cos(s.omega * s.t))]
class CarElement(Element):
"""Car on a road whose position `x` advances at its speed `v`."""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
x: Annotated[object, StateSpec(role='position')] = 0.0,
v: Annotated[object, StateSpec(role='velocity')] = 0.0,
) -> None:
super().__init__(label, visual=visual)
self.x = x
self.v = v
def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
"""Return the advance of the position at the speed."""
return [RateLaw(s.x, s.v)]
class DelayedFollowingInteraction(RateInteraction):
"""Acceleration of a follower car by the speed difference to its leader, both seen a reaction time late.
The speed of the follower follows the Gazis-Herman-Rothery law
`v_f'(t) = λ (v_l - v_f)(t - T) / (x_l - x_f)(t - T)^m` of the sensitivity `λ`, the reaction time `T`, and the
power `m` of the gap.
Args:
label:
The label of the interaction.
target:
The path of the follower car.
leader:
The path of the car ahead of the follower.
reaction:
The sensitivity `λ`.
T:
The reaction time.
power:
The power `m` of the gap.
"""
def __init__(
self,
label: str,
*,
target: Annotated[str, RoleSpec()],
leader: Annotated[str, RoleSpec()],
reaction: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
T: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
power: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
) -> None:
super().__init__(label, target=target, state='v')
self.leader = leader
self.reaction = reaction
self.T = T
self.power = power
def _get_rate(self, namespace: SimpleNamespace) -> object:
"""Return the delayed response of the follower to its leader."""
s, leader, follower = namespace, namespace.leader, namespace.target
gap = delayed(leader.x, s.T) - delayed(follower.x, s.T)
return s.reaction * (delayed(leader.v, s.T) - delayed(follower.v, s.T)) / gap**s.power
Equations
car_0.x: diff(x_car_0(t), t) == v_car_0(t)
car_0.v: diff(v_car_0(t), t) == -amplitude_car_0*omega_car_0*u_car_0*cos(omega_car_0*t)
car_1.x: diff(x_car_1(t), t) == v_car_1(t)
car_1.v: diff(v_car_1(t), t) == reaction_follow_1*(v_car_0(-T_follow_1 + t) - v_car_1(-T_follow_1 + t))/(x_car_0(-T_follow_1 + t) - x_car_1(-T_follow_1 + t))^power_follow_1
car_2.x: diff(x_car_2(t), t) == v_car_2(t)
car_2.v: diff(v_car_2(t), t) == reaction_follow_2*(v_car_1(-T_follow_2 + t) - v_car_2(-T_follow_2 + t))/(x_car_1(-T_follow_2 + t) - x_car_2(-T_follow_2 + t))^power_follow_2
car_3.x: diff(x_car_3(t), t) == v_car_3(t)
car_3.v: diff(v_car_3(t), t) == reaction_follow_3*(v_car_2(-T_follow_3 + t) - v_car_3(-T_follow_3 + t))/(x_car_2(-T_follow_3 + t) - x_car_3(-T_follow_3 + t))^power_follow_3
car_4.x: diff(x_car_4(t), t) == v_car_4(t)
car_4.v: diff(v_car_4(t), t) == reaction_follow_4*(v_car_3(-T_follow_4 + t) - v_car_4(-T_follow_4 + t))/(x_car_3(-T_follow_4 + t) - x_car_4(-T_follow_4 + t))^power_follow_4
car_5.x: diff(x_car_5(t), t) == v_car_5(t)
car_5.v: diff(v_car_5(t), t) == reaction_follow_5*(v_car_4(-T_follow_5 + t) - v_car_5(-T_follow_5 + t))/(x_car_4(-T_follow_5 + t) - x_car_5(-T_follow_5 + t))^power_follow_5
car_6.x: diff(x_car_6(t), t) == v_car_6(t)
car_6.v: diff(v_car_6(t), t) == reaction_follow_6*(v_car_5(-T_follow_6 + t) - v_car_6(-T_follow_6 + t))/(x_car_5(-T_follow_6 + t) - x_car_6(-T_follow_6 + t))^power_follow_6
car_7.x: diff(x_car_7(t), t) == v_car_7(t)
car_7.v: diff(v_car_7(t), t) == reaction_follow_7*(v_car_6(-T_follow_7 + t) - v_car_7(-T_follow_7 + t))/(x_car_6(-T_follow_7 + t) - x_car_7(-T_follow_7 + t))^power_follow_7